Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e39c8ce
update notifications components designs
christian-byrne Oct 26, 2025
7430772
fix stylelint issues
christian-byrne Oct 26, 2025
593edd5
fix test locator
christian-byrne Oct 26, 2025
a24643a
update release notification components with semantic design tokens
christian-byrne Oct 29, 2025
40ba53d
fix notification component tests and remove temporary mock data
christian-byrne Nov 6, 2025
f155a18
implement new designs
christian-byrne Nov 7, 2025
87d434c
fix test
christian-byrne Nov 7, 2025
e7c9cdf
update style
christian-byrne Nov 13, 2025
e563ca5
use correct tokens
christian-byrne Nov 13, 2025
4b3ccd9
remove border radius token
christian-byrne Nov 14, 2025
c370386
[feat] Implement proper design tokens and Storybook stories for relea…
christian-byrne Nov 27, 2025
5c514eb
add i18n
christian-byrne Nov 27, 2025
1faf41b
[fix] Fix notification component tests and improve empty content hand…
christian-byrne Nov 30, 2025
57bf2ae
[security] Fix XSS vulnerability in notification components
christian-byrne Nov 30, 2025
6f3abc7
[fix] Address nitpick comments
christian-byrne Dec 4, 2025
062b1a7
Remove merge marker
DrJKL Dec 4, 2025
b1ab138
fix: remove inappropriate update button from WhatsNewPopup and add sc…
christian-byrne Dec 6, 2025
468335c
refactor: remove inappropriate image stories from toast component
christian-byrne Dec 9, 2025
ee89a3a
[fix] resolve merge conflict markers in main.json
christian-byrne Dec 9, 2025
a7d949a
[fix] remove TypeScript any casts and improve type safety in tests
christian-byrne Dec 9, 2025
892d785
[fix] correct HTML rel attribute, use semantic CSS token, prevent tes…
christian-byrne Dec 9, 2025
0724df8
[fix] use i18n for hardcoded strings, fix Storybook vitest mock, clea…
christian-byrne Dec 9, 2025
b83b4e5
[fix] add recentRelease mock to Storybook for proper component rendering
christian-byrne Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update release notification components with semantic design tokens
- Add proper semantic design tokens (corner-radius, blue surface)
- Convert hardcoded colors to theme-aware tokens in toast and popup
- Implement Figma-compliant styling with image support
- Add comprehensive component tests for behavioral validation
- Update translation strings for improved UI copy
  • Loading branch information
christian-byrne committed Dec 9, 2025
commit a24643a0610c00e2bb5129cbfabe8c5f51dfbf3d
7 changes: 7 additions & 0 deletions packages/design-system/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@
--color-danger-100: #c02323;
--color-danger-200: #d62952;

/* Light theme specific tokens */
--color-interface-menu-keybind-surface-blue: #78BAE9;

/* Corner radius tokens */
--corner-radius-corner-radius-md: 8px;
--base-corner-radius-corner-radius-md: 8px;

--color-coral-red-600: #973a40;
--color-coral-red-500: #c53f49;
--color-coral-red-400: #dd424e;
Expand Down
9 changes: 6 additions & 3 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -763,10 +763,11 @@
"reinstall": "Re-Install"
},
"releaseToast": {
"newVersionAvailable": "New Version Available!",
"whatsNew": "What's New?",
"newVersionAvailable": "New update is out!",
"whatsNew": "See what's new",
"skip": "Skip",
"update": "Update"
"update": "Update",
"description": "Check out the latest improvements and features in this update."
},
"menu": {
"hideMenu": "Hide Menu",
Expand Down Expand Up @@ -1928,6 +1929,8 @@
},
"whatsNewPopup": {
"learnMore": "Learn more",
"later": "Later",
"update": "Update",
"noReleaseNotes": "No release notes available."
},
"breadcrumbsMenu": {
Expand Down
240 changes: 240 additions & 0 deletions src/platform/updates/components/ReleaseNotificationToast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'

import enMessages from '@/locales/en/main.json' with { type: 'json' }

import type { ReleaseNote } from '../common/releaseService'
import ReleaseNotificationToast from './ReleaseNotificationToast.vue'

// Mock dependencies
vi.mock('@/utils/formatUtil', () => ({
formatVersionAnchor: vi.fn((version: string) => version.replace(/\./g, ''))
}))

vi.mock('@/utils/markdownRendererUtil', () => ({
renderMarkdownToHtml: vi.fn((content: string) => `<div>${content}</div>`)
}))

// Mock release store
const mockReleaseStore = {
recentRelease: null as ReleaseNote | null,
shouldShowToast: false,
handleSkipRelease: vi.fn(),
handleShowChangelog: vi.fn(),
releases: [],
fetchReleases: vi.fn()
}

vi.mock('../common/releaseStore', () => ({
useReleaseStore: vi.fn(() => mockReleaseStore)
}))

describe('ReleaseNotificationToast', () => {
let wrapper: VueWrapper

const i18n = createI18n({
legacy: false,
locale: 'en',
messages: { en: enMessages }
})

const mountComponent = (props = {}) => {
return mount(ReleaseNotificationToast, {
global: {
plugins: [i18n],
stubs: {
// Stub Lucide icons
'i-lucide-rocket': true,
'i-lucide-external-link': true
}
},
props
})
}

beforeEach(() => {
vi.clearAllMocks()
// Reset store state
mockReleaseStore.recentRelease = null
mockReleaseStore.shouldShowToast = true // Force show for testing
})

it('renders correctly when shouldShow is true', () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release\n\nSome content'
} as ReleaseNote

wrapper = mountComponent()
expect(wrapper.find('.release-notification-toast').exists()).toBe(true)
})

it('displays rocket icon', () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()
expect(wrapper.find('.icon-\\[lucide--rocket\\]').exists()).toBe(true)
})

it('displays release version', () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()
expect(wrapper.text()).toContain('1.2.3')
})

it('calls handleSkipRelease when skip button is clicked', async () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()

const skipButton = wrapper.find('.action-secondary')
await skipButton.trigger('click')

expect(mockReleaseStore.handleSkipRelease).toHaveBeenCalledWith('1.2.3')
})

it('opens update URL when update button is clicked', async () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

// Mock window.open
const mockWindowOpen = vi.fn()
vi.stubGlobal('window', { open: mockWindowOpen })

wrapper = mountComponent()

const updateButton = wrapper.find('.action-primary')
await updateButton.trigger('click')

expect(mockWindowOpen).toHaveBeenCalledWith(
'https://docs.comfy.org/installation/update_comfyui',
'_blank'
)
})

it('calls handleShowChangelog when learn more link is clicked', async () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()

const learnMoreLink = wrapper.find('.learn-more-link')
await learnMoreLink.trigger('click')

expect(mockReleaseStore.handleShowChangelog).toHaveBeenCalledWith('1.2.3')
})

it('generates correct changelog URL', () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()

const learnMoreLink = wrapper.find('.learn-more-link')
expect(learnMoreLink.attributes('href')).toContain('docs.comfy.org/changelog')
})

it('removes title from markdown content for toast display', () => {
const mockMarkdownRenderer = vi.mocked(vi.importMock('@/utils/markdownRendererUtil')).renderMarkdownToHtml
mockMarkdownRenderer.mockReturnValue('<div>Content without title</div>')

mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release Title\n\nSome content'
} as ReleaseNote

wrapper = mountComponent()

// Should call markdown renderer with title removed
expect(mockMarkdownRenderer).toHaveBeenCalledWith('\n\nSome content')
})

it('fetches releases on mount when not already loaded', async () => {
mockReleaseStore.releases = [] // Empty releases array

wrapper = mountComponent()

expect(mockReleaseStore.fetchReleases).toHaveBeenCalled()
})

it('handles missing release content gracefully', () => {
mockReleaseStore.recentRelease = {
version: '1.2.3',
content: ''
} as ReleaseNote

wrapper = mountComponent()

// Should render fallback content
const descriptionElement = wrapper.find('.toast-description')
expect(descriptionElement.exists()).toBe(true)
})

it('auto-hides after timeout', async () => {
vi.useFakeTimers()

mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()

// Initially visible
expect(wrapper.find('.release-notification-toast').exists()).toBe(true)

// Fast-forward time
vi.advanceTimersByTime(8000)
await wrapper.vm.$nextTick()

// Should be dismissed
expect(wrapper.find('.release-notification-toast').exists()).toBe(false)

vi.useRealTimers()
})

it('clears auto-hide timer when manually dismissed', async () => {
vi.useFakeTimers()

mockReleaseStore.recentRelease = {
version: '1.2.3',
content: '# Test Release'
} as ReleaseNote

wrapper = mountComponent()

// Start the timer
vi.advanceTimersByTime(1000)

// Manually dismiss by clicking skip
const skipButton = wrapper.find('.action-secondary')
await skipButton.trigger('click')

// Timer should be cleared, so advancing time shouldn't auto-dismiss
vi.advanceTimersByTime(10000)
await wrapper.vm.$nextTick()

// Verify the store method was called (manual dismissal)
expect(mockReleaseStore.handleSkipRelease).toHaveBeenCalled()

vi.useRealTimers()
})
})
Loading