test: address PR review feedback on desktop-ui unit tests

- Assert isRunningInstallationFix resets to false after execution
- Verify specific URLs in window.open assertions
- Fix invalid slot HTML in TaskCard stub template
- Assert concrete normalized URL value instead of typeof check
- Eliminate renderCard duplication via optional task param
- Remove toHaveProperty change-detector test per project guidelines
- Replace as unknown as Terminal with Pick<Terminal, 'write'>
- Extract shared withSetup helper to src/test/withSetup.ts
- Remove initial state change-detector tests per project guidelines
This commit is contained in:
Kelly Yang
2026-04-15 20:41:20 -07:00
parent 5dd03c2a4c
commit 0a96ffb37c
9 changed files with 54 additions and 123 deletions

View File

@@ -163,7 +163,7 @@ describe('UrlInput', () => {
await user.tab()
const emittedUrl = onUpdate.mock.calls[0]?.[0]
expect(typeof emittedUrl).toBe('string')
expect(emittedUrl).toBe('https://example.com/')
})
})

View File

@@ -33,7 +33,17 @@ const baseTask: MaintenanceTask = {
execute: vi.fn().mockResolvedValue(true)
}
function renderCard(state: 'OK' | 'error' | 'warning' | 'skipped') {
const cardStubs = {
Card: {
template: '<div data-testid="card"><slot name="content"></slot></div>'
},
Button: { template: '<button />' }
}
function renderCard(
state: 'OK' | 'error' | 'warning' | 'skipped',
task: MaintenanceTask = baseTask
) {
mockGetRunner.mockReturnValue({
state,
executing: false,
@@ -42,18 +52,10 @@ function renderCard(state: 'OK' | 'error' | 'warning' | 'skipped') {
})
return render(TaskCard, {
props: { task: baseTask },
props: { task },
global: {
plugins: [[PrimeVue, { unstyled: true }]],
stubs: {
Card: {
template:
'<div data-testid="card"><slot name="content" /></slot></div>'
},
Button: {
template: '<button />'
}
}
stubs: cardStubs
}
})
}
@@ -80,26 +82,7 @@ describe('TaskCard', () => {
...baseTask,
errorDescription: undefined
}
mockGetRunner.mockReturnValue({
state: 'error',
executing: false,
refreshing: false
})
render(TaskCard, {
props: { task: taskWithoutErrorDesc },
global: {
plugins: [[PrimeVue, { unstyled: true }]],
stubs: {
Card: {
template:
'<div data-testid="card"><slot name="content" /></slot></div>'
},
Button: { template: '<button />' }
}
}
})
renderCard('error', taskWithoutErrorDesc)
expect(screen.getByText('Short description')).toBeDefined()
})
})

View File

@@ -1,6 +1,5 @@
import { render } from '@testing-library/vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, ref } from 'vue'
import { ref } from 'vue'
const { mockTerminal, MockTerminal, mockFitAddon, MockFitAddon } = vi.hoisted(
() => {
@@ -33,22 +32,9 @@ vi.mock('@xterm/xterm', () => ({ Terminal: MockTerminal }))
vi.mock('@xterm/addon-fit', () => ({ FitAddon: MockFitAddon }))
vi.mock('@xterm/xterm/css/xterm.css', () => ({}))
import { withSetup } from '@/test/withSetup'
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
function withSetup<T>(composable: () => T): T {
let result!: T
render(
defineComponent({
setup() {
result = composable()
return {}
},
template: '<div />'
})
)
return result
}
function getKeyHandler(): (event: KeyboardEvent) => boolean {
return mockTerminal.attachCustomKeyEventHandler.mock.calls[0][0]
}

View File

@@ -1,6 +1,4 @@
import { render } from '@testing-library/vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent } from 'vue'
const { mockSerialize, MockSerializeAddon } = vi.hoisted(() => {
const mockSerialize = vi.fn<[], string>()
@@ -21,22 +19,9 @@ vi.mock('@xterm/addon-serialize', () => ({
}))
import type { Terminal } from '@xterm/xterm'
import { withSetup } from '@/test/withSetup'
import { useTerminalBuffer } from '@/composables/bottomPanelTabs/useTerminalBuffer'
function withSetup<T>(composable: () => T): T {
let result!: T
render(
defineComponent({
setup() {
result = composable()
return {}
},
template: '<div />'
})
)
return result
}
describe('useTerminalBuffer', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -48,7 +33,7 @@ describe('useTerminalBuffer', () => {
mockSerialize.mockReturnValue('hello world')
const { copyTo } = withSetup(() => useTerminalBuffer())
const mockWrite = vi.fn()
copyTo({ write: mockWrite } as unknown as Terminal)
copyTo({ write: mockWrite } as Pick<Terminal, 'write'>)
expect(mockWrite).toHaveBeenCalledWith('hello world')
})
@@ -56,7 +41,7 @@ describe('useTerminalBuffer', () => {
mockSerialize.mockReturnValue('')
const { copyTo } = withSetup(() => useTerminalBuffer())
const mockWrite = vi.fn()
copyTo({ write: mockWrite } as unknown as Terminal)
copyTo({ write: mockWrite } as Pick<Terminal, 'write'>)
expect(mockWrite).toHaveBeenCalledWith('')
})
})

View File

@@ -25,15 +25,6 @@ describe('getDialog', () => {
expect(result.id).toBe('invalidDialog')
})
it('includes id, title, message, and buttons in the result', () => {
const result = getDialog('reinstallVenv')
expect(result).toHaveProperty('id')
expect(result).toHaveProperty('title')
expect(result).toHaveProperty('message')
expect(result).toHaveProperty('buttons')
expect(Array.isArray(result.buttons)).toBe(true)
})
it('returns a deep clone — mutations do not affect the original', () => {
const result = getDialog('reinstallVenv')
const originalFirstLabel = DESKTOP_DIALOGS.reinstallVenv.buttons[0].label

View File

@@ -48,19 +48,28 @@ describe('desktopMaintenanceTasks', () => {
})
describe('URL-opening tasks', () => {
it('git execute opens a browser tab', () => {
it('git execute opens the git download page', () => {
findTask('git').execute()
expect(window.open).toHaveBeenCalledOnce()
expect(window.open).toHaveBeenCalledWith(
'https://git-scm.com/downloads/',
'_blank'
)
})
it('uv execute opens a browser tab', () => {
it('uv execute opens the uv installation page', () => {
findTask('uv').execute()
expect(window.open).toHaveBeenCalledOnce()
expect(window.open).toHaveBeenCalledWith(
'https://docs.astral.sh/uv/getting-started/installation/',
'_blank'
)
})
it('vcRedist execute opens a browser tab', () => {
it('vcRedist execute opens the VC++ redistributable download', () => {
findTask('vcRedist').execute()
expect(window.open).toHaveBeenCalledOnce()
expect(window.open).toHaveBeenCalledWith(
'https://aka.ms/vs/17/release/vc_redist.x64.exe',
'_blank'
)
})
})
})

View File

@@ -68,32 +68,6 @@ describe('useMaintenanceTaskStore', () => {
store = createStore()
})
describe('initial state', () => {
it('creates runners for all tasks', () => {
expect(store.tasks.length).toBe(testTasks.length)
})
it('starts with isRefreshing false', () => {
expect(store.isRefreshing).toBe(false)
})
it('starts with no errors', () => {
expect(store.anyErrors).toBe(false)
})
it('starts with unsafeBasePath false', () => {
expect(store.unsafeBasePath).toBe(false)
})
it('starts with no running terminal commands', () => {
expect(store.isRunningTerminalCommand).toBe(false)
})
it('starts with no running installation fixes', () => {
expect(store.isRunningInstallationFix).toBe(false)
})
})
describe('processUpdate', () => {
it('sets isRefreshing to true during in-progress update', () => {
store.processUpdate(makeUpdate({ inProgress: true }))
@@ -308,6 +282,7 @@ describe('useMaintenanceTaskStore', () => {
resolveTask(true)
await executePromise
expect(store.isRunningInstallationFix).toBe(false)
})
})
})

View File

@@ -0,0 +1,16 @@
import { render } from '@testing-library/vue'
import { defineComponent } from 'vue'
export function withSetup<T>(composable: () => T): T {
let result!: T
render(
defineComponent({
setup() {
result = composable()
return {}
},
template: '<div />'
})
)
return result
}

View File

@@ -1,23 +1,9 @@
import { render } from '@testing-library/vue'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, defineComponent, nextTick, ref } from 'vue'
import { computed, nextTick, ref } from 'vue'
import { withSetup } from '@/test/withSetup'
import { useMinLoadingDurationRef } from '@/utils/refUtil'
function withSetup<T>(composable: () => T): T {
let result!: T
render(
defineComponent({
setup() {
result = composable()
return {}
},
template: '<div />'
})
)
return result
}
describe('useMinLoadingDurationRef', () => {
beforeEach(() => {
vi.useFakeTimers()