363 lines
9.3 KiB
TypeScript
363 lines
9.3 KiB
TypeScript
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
|
|
import { renderHook, act, waitFor } from '@testing-library/react'
|
|
import { useReleaseNotes } from '../useReleaseNotes'
|
|
|
|
// Mock fetch
|
|
const mockFetch = vi.fn()
|
|
global.fetch = mockFetch
|
|
|
|
describe('useReleaseNotes', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
// Reset store state to defaults
|
|
useReleaseNotes.setState({
|
|
release: null,
|
|
loading: false,
|
|
error: null,
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.resetAllMocks()
|
|
})
|
|
|
|
it('should initialize with default values', () => {
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
expect(result.current.release).toBe(null)
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
expect(typeof result.current.fetchLatestRelease).toBe('function')
|
|
})
|
|
|
|
describe('fetchLatestRelease', () => {
|
|
it('should fetch stable release when includeBeta is false', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v2.0.0-beta.1',
|
|
prerelease: true,
|
|
draft: false,
|
|
body: 'Beta release notes',
|
|
},
|
|
{
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
},
|
|
{
|
|
tag_name: 'v1.4.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Previous stable release',
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.github.com/repos/janhq/jan/releases'
|
|
)
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
expect(result.current.release).toEqual({
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
})
|
|
})
|
|
|
|
it('should fetch beta release when includeBeta is true', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v2.0.0-beta.1',
|
|
prerelease: true,
|
|
draft: false,
|
|
body: 'Beta release notes',
|
|
},
|
|
{
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(true)
|
|
})
|
|
|
|
expect(result.current.release).toEqual({
|
|
tag_name: 'v2.0.0-beta.1',
|
|
prerelease: true,
|
|
draft: false,
|
|
body: 'Beta release notes',
|
|
})
|
|
})
|
|
|
|
it('should fallback to stable release when includeBeta is true but no beta exists', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
},
|
|
{
|
|
tag_name: 'v1.4.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Previous stable release',
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(true)
|
|
})
|
|
|
|
expect(result.current.release).toEqual({
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
})
|
|
})
|
|
|
|
it('should ignore draft releases', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v2.0.0-draft',
|
|
prerelease: false,
|
|
draft: true,
|
|
body: 'Draft release',
|
|
},
|
|
{
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.release).toEqual({
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Stable release notes',
|
|
})
|
|
})
|
|
|
|
it('should set loading state during fetch', async () => {
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => [],
|
|
})
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
})
|
|
|
|
it('should handle fetch errors', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('Network error'))
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe('Network error')
|
|
expect(result.current.release).toBe(null)
|
|
})
|
|
|
|
it('should handle HTTP errors', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe('Failed to fetch releases')
|
|
expect(result.current.release).toBe(null)
|
|
})
|
|
|
|
it('should handle empty releases array', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => [],
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
expect(result.current.release).toBe(undefined)
|
|
})
|
|
|
|
it('should clear previous error on new fetch', async () => {
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
// First request fails
|
|
mockFetch.mockRejectedValueOnce(new Error('First error'))
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.error).toBe('First error')
|
|
|
|
// Second request succeeds
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => [
|
|
{
|
|
tag_name: 'v1.0.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Release notes',
|
|
},
|
|
],
|
|
})
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.error).toBe(null)
|
|
expect(result.current.release).toEqual({
|
|
tag_name: 'v1.0.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Release notes',
|
|
})
|
|
})
|
|
|
|
it('should handle releases with additional properties', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v1.5.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Release notes',
|
|
published_at: '2024-01-01T00:00:00Z',
|
|
html_url: 'https://github.com/janhq/jan/releases/tag/v1.5.0',
|
|
assets: [],
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.release).toEqual(mockReleases[0])
|
|
})
|
|
})
|
|
|
|
describe('state management', () => {
|
|
it('should maintain state across multiple hook instances', () => {
|
|
const { result: result1 } = renderHook(() => useReleaseNotes())
|
|
const { result: result2 } = renderHook(() => useReleaseNotes())
|
|
|
|
expect(result1.current.release).toBe(result2.current.release)
|
|
expect(result1.current.loading).toBe(result2.current.loading)
|
|
expect(result1.current.error).toBe(result2.current.error)
|
|
})
|
|
})
|
|
|
|
describe('multiple requests', () => {
|
|
it('should handle sequential requests', async () => {
|
|
const mockReleases = [
|
|
{
|
|
tag_name: 'v1.0.0',
|
|
prerelease: false,
|
|
draft: false,
|
|
body: 'Release notes',
|
|
},
|
|
]
|
|
|
|
mockFetch.mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockReleases,
|
|
})
|
|
|
|
const { result } = renderHook(() => useReleaseNotes())
|
|
|
|
// First request
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(false)
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
|
|
// Second request
|
|
await act(async () => {
|
|
await result.current.fetchLatestRelease(true)
|
|
})
|
|
|
|
expect(mockFetch).toHaveBeenCalledTimes(2)
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.error).toBe(null)
|
|
})
|
|
})
|
|
})
|