refactor : merge Inventory_frontend submodule into frontend/ directory
Merges the full git history of Inventory_frontend into the monorepo under frontend/. Removes the submodule in favor of a unified repo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
frontend/tests/composables/useConfirm.test.ts
Normal file
81
frontend/tests/composables/useConfirm.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
|
||||
describe('useConfirm', () => {
|
||||
it('returns confirm function and state', () => {
|
||||
const { confirm, confirmState, handleConfirm, handleCancel } = useConfirm()
|
||||
expect(typeof confirm).toBe('function')
|
||||
expect(typeof handleConfirm).toBe('function')
|
||||
expect(typeof handleCancel).toBe('function')
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('opens modal with correct options', () => {
|
||||
const { confirm, confirmState } = useConfirm()
|
||||
// Don't await — we'll manually resolve
|
||||
confirm({ message: 'Delete this item?' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
expect(confirmState.message).toBe('Delete this item?')
|
||||
expect(confirmState.title).toBe('Confirmation')
|
||||
expect(confirmState.confirmText).toBe('Supprimer')
|
||||
expect(confirmState.cancelText).toBe('Annuler')
|
||||
expect(confirmState.dangerous).toBe(true)
|
||||
// Clean up by canceling
|
||||
const { handleCancel } = useConfirm()
|
||||
handleCancel()
|
||||
})
|
||||
|
||||
it('resolves true on confirm', async () => {
|
||||
const { confirm, handleConfirm } = useConfirm()
|
||||
const promise = confirm({ message: 'Confirm?' })
|
||||
handleConfirm()
|
||||
const result = await promise
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('resolves false on cancel', async () => {
|
||||
const { confirm, handleCancel } = useConfirm()
|
||||
const promise = confirm({ message: 'Cancel?' })
|
||||
handleCancel()
|
||||
const result = await promise
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('closes modal after confirm', async () => {
|
||||
const { confirm, confirmState, handleConfirm } = useConfirm()
|
||||
confirm({ message: 'Test' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
handleConfirm()
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('closes modal after cancel', async () => {
|
||||
const { confirm, confirmState, handleCancel } = useConfirm()
|
||||
confirm({ message: 'Test' })
|
||||
expect(confirmState.open).toBe(true)
|
||||
handleCancel()
|
||||
expect(confirmState.open).toBe(false)
|
||||
})
|
||||
|
||||
it('supports custom options', () => {
|
||||
const { confirm, confirmState, handleCancel } = useConfirm()
|
||||
confirm({
|
||||
title: 'Custom Title',
|
||||
message: 'Custom message',
|
||||
confirmText: 'Yes',
|
||||
cancelText: 'No',
|
||||
dangerous: false,
|
||||
})
|
||||
expect(confirmState.title).toBe('Custom Title')
|
||||
expect(confirmState.confirmText).toBe('Yes')
|
||||
expect(confirmState.cancelText).toBe('No')
|
||||
expect(confirmState.dangerous).toBe(false)
|
||||
handleCancel()
|
||||
})
|
||||
|
||||
it('shares state across calls (singleton)', () => {
|
||||
const a = useConfirm()
|
||||
const b = useConfirm()
|
||||
expect(a.confirmState).toBe(b.confirmState)
|
||||
})
|
||||
})
|
||||
412
frontend/tests/composables/useEntityTypes.test.ts
Normal file
412
frontend/tests/composables/useEntityTypes.test.ts
Normal file
@@ -0,0 +1,412 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
import { useEntityTypes, invalidateEntityTypeCache } from '~/composables/useEntityTypes'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mocks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mockShowSuccess = vi.fn()
|
||||
const mockShowError = vi.fn()
|
||||
|
||||
vi.mock('~/composables/useToast', () => ({
|
||||
useToast: () => ({
|
||||
showSuccess: mockShowSuccess,
|
||||
showError: mockShowError,
|
||||
showInfo: vi.fn(),
|
||||
showToast: vi.fn(),
|
||||
toasts: { value: [] },
|
||||
clearAll: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockListModelTypes = vi.fn()
|
||||
const mockCreateModelType = vi.fn()
|
||||
const mockUpdateModelType = vi.fn()
|
||||
const mockDeleteModelType = vi.fn()
|
||||
|
||||
vi.mock('~/services/modelTypes', () => ({
|
||||
listModelTypes: (...args: any[]) => mockListModelTypes(...args),
|
||||
createModelType: (...args: any[]) => mockCreateModelType(...args),
|
||||
updateModelType: (...args: any[]) => mockUpdateModelType(...args),
|
||||
deleteModelType: (...args: any[]) => mockDeleteModelType(...args),
|
||||
}))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const fakeItem = (id: string, name: string) => ({
|
||||
id,
|
||||
name,
|
||||
code: name.toLowerCase(),
|
||||
category: 'COMPONENT' as const,
|
||||
structure: null,
|
||||
createdAt: '2025-01-01',
|
||||
updatedAt: '2025-01-01',
|
||||
notes: null,
|
||||
description: null,
|
||||
})
|
||||
|
||||
function resetState() {
|
||||
// Reset singleton state via public APIs
|
||||
const comp = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
comp.types.value = []
|
||||
invalidateEntityTypeCache('COMPONENT')
|
||||
|
||||
const piece = useEntityTypes({ category: 'PIECE', label: 'pièce' })
|
||||
piece.types.value = []
|
||||
invalidateEntityTypeCache('PIECE')
|
||||
|
||||
const product = useEntityTypes({ category: 'PRODUCT', label: 'produit' })
|
||||
product.types.value = []
|
||||
invalidateEntityTypeCache('PRODUCT')
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetState()
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// loadTypes
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('loadTypes', () => {
|
||||
it('fetches from API and stores in types', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Type A'), fakeItem('2', 'Type B')],
|
||||
total: 2,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const { loadTypes, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const result = await loadTypes()
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(types.value).toHaveLength(2)
|
||||
expect(types.value[0].name).toBe('Type A')
|
||||
expect(mockListModelTypes).toHaveBeenCalledOnce()
|
||||
expect(mockListModelTypes).toHaveBeenCalledWith({
|
||||
category: 'COMPONENT',
|
||||
sort: 'name',
|
||||
dir: 'asc',
|
||||
limit: 200,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns cached data on second call without force', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Cached')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const { loadTypes, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Second call — should return cache
|
||||
const result = await loadTypes()
|
||||
expect(result.success).toBe(true)
|
||||
expect(types.value).toHaveLength(1)
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(1) // NOT called again
|
||||
})
|
||||
|
||||
it('refetches with force: true', async () => {
|
||||
mockListModelTypes
|
||||
.mockResolvedValueOnce({ items: [fakeItem('1', 'Old')], total: 1, offset: 0, limit: 200 })
|
||||
.mockResolvedValueOnce({ items: [fakeItem('1', 'New')], total: 1, offset: 0, limit: 200 })
|
||||
|
||||
const { loadTypes, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
expect(types.value[0].name).toBe('Old')
|
||||
|
||||
await loadTypes({ force: true })
|
||||
expect(types.value[0].name).toBe('New')
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('sets loading during fetch', async () => {
|
||||
let resolvePromise: (value: any) => void
|
||||
mockListModelTypes.mockReturnValue(new Promise((resolve) => { resolvePromise = resolve }))
|
||||
|
||||
const { loadTypes, loading } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
expect(loading.value).toBe(false)
|
||||
|
||||
const promise = loadTypes()
|
||||
expect(loading.value).toBe(true)
|
||||
|
||||
resolvePromise!({ items: [], total: 0, offset: 0, limit: 200 })
|
||||
await promise
|
||||
expect(loading.value).toBe(false)
|
||||
})
|
||||
|
||||
it('shows error on API failure', async () => {
|
||||
mockListModelTypes.mockRejectedValue(new Error('Network error'))
|
||||
|
||||
const { loadTypes, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const result = await loadTypes()
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toBe('Network error')
|
||||
expect(types.value).toEqual([])
|
||||
expect(mockShowError).toHaveBeenCalledWith(
|
||||
'Impossible de charger les types de composant.',
|
||||
)
|
||||
})
|
||||
|
||||
it('normalizes items with description fallback', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [{ ...fakeItem('1', 'Test'), notes: 'From notes', description: null }],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const { loadTypes, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
|
||||
expect(types.value[0].description).toBe('From notes')
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createType
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('createType', () => {
|
||||
it('creates and pushes to types array', async () => {
|
||||
mockCreateModelType.mockResolvedValue(fakeItem('new-1', 'Created'))
|
||||
|
||||
const { createType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const result = await createType({ name: 'Created' })
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(types.value).toHaveLength(1)
|
||||
expect(types.value[0].name).toBe('Created')
|
||||
expect(mockShowSuccess).toHaveBeenCalledWith(expect.stringContaining('Created'))
|
||||
})
|
||||
|
||||
it('sends correct payload with auto-generated code', async () => {
|
||||
mockCreateModelType.mockResolvedValue(fakeItem('new-1', 'Ma Catégorie'))
|
||||
|
||||
const { createType } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await createType({ name: 'Ma Catégorie' })
|
||||
|
||||
expect(mockCreateModelType).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'Ma Catégorie',
|
||||
code: 'ma-categorie',
|
||||
category: 'COMPONENT',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('uses provided code if available', async () => {
|
||||
mockCreateModelType.mockResolvedValue(fakeItem('new-1', 'Test'))
|
||||
|
||||
const { createType } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await createType({ name: 'Test', code: 'custom-code' })
|
||||
|
||||
expect(mockCreateModelType).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ code: 'custom-code' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('shows error on API failure without modifying types', async () => {
|
||||
mockCreateModelType.mockRejectedValue(new Error('Create failed'))
|
||||
|
||||
const { createType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const result = await createType({ name: 'Fail' })
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(types.value).toEqual([])
|
||||
expect(mockShowError).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// updateType
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('updateType', () => {
|
||||
it('updates the existing item in types array', async () => {
|
||||
// Pre-populate
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Original')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
const { loadTypes, updateType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
|
||||
mockUpdateModelType.mockResolvedValue(fakeItem('1', 'Updated'))
|
||||
const result = await updateType('1', { name: 'Updated' })
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(types.value).toHaveLength(1)
|
||||
expect(types.value[0].name).toBe('Updated')
|
||||
expect(mockShowSuccess).toHaveBeenCalledWith(expect.stringContaining('Updated'))
|
||||
})
|
||||
|
||||
it('shows error on API failure without modifying types', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Original')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
const { loadTypes, updateType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
|
||||
mockUpdateModelType.mockRejectedValue(new Error('Update failed'))
|
||||
const result = await updateType('1', { name: 'Bad' })
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(types.value[0].name).toBe('Original')
|
||||
expect(mockShowError).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// deleteType
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('deleteType', () => {
|
||||
it('removes item from types array', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'A'), fakeItem('2', 'B')],
|
||||
total: 2,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
const { loadTypes, deleteType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
expect(types.value).toHaveLength(2)
|
||||
|
||||
mockDeleteModelType.mockResolvedValue(undefined)
|
||||
const result = await deleteType('1')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(types.value).toHaveLength(1)
|
||||
expect(types.value[0].id).toBe('2')
|
||||
expect(mockShowSuccess).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows error on API failure without removing item', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Keep')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
const { loadTypes, deleteType, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
|
||||
mockDeleteModelType.mockRejectedValue(new Error('Delete failed'))
|
||||
const result = await deleteType('1')
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(types.value).toHaveLength(1)
|
||||
expect(mockShowError).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// invalidateEntityTypeCache
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('invalidateEntityTypeCache', () => {
|
||||
it('forces next loadTypes to refetch', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Cached')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const { loadTypes } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadTypes()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Without invalidation, wouldn't refetch
|
||||
invalidateEntityTypeCache('COMPONENT')
|
||||
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Fresh')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const { loadTypes: loadAgain, types } = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await loadAgain()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(2)
|
||||
expect(types.value[0].name).toBe('Fresh')
|
||||
})
|
||||
|
||||
it('does not crash for unknown category', () => {
|
||||
expect(() => invalidateEntityTypeCache('COMPONENT')).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Singleton per category
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('singleton per category', () => {
|
||||
it('shares state between same category calls', async () => {
|
||||
mockListModelTypes.mockResolvedValue({
|
||||
items: [fakeItem('1', 'Shared')],
|
||||
total: 1,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const a = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
await a.loadTypes()
|
||||
|
||||
const b = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
expect(b.types.value).toHaveLength(1)
|
||||
expect(b.types.value[0].name).toBe('Shared')
|
||||
})
|
||||
|
||||
it('isolates state between different categories', async () => {
|
||||
mockListModelTypes
|
||||
.mockResolvedValueOnce({ items: [fakeItem('c1', 'Component')], total: 1, offset: 0, limit: 200 })
|
||||
.mockResolvedValueOnce({ items: [fakeItem('p1', 'Piece')], total: 1, offset: 0, limit: 200 })
|
||||
|
||||
const comp = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const piece = useEntityTypes({ category: 'PIECE', label: 'pièce' })
|
||||
|
||||
await comp.loadTypes()
|
||||
await piece.loadTypes()
|
||||
|
||||
expect(comp.types.value).toHaveLength(1)
|
||||
expect(comp.types.value[0].name).toBe('Component')
|
||||
expect(piece.types.value).toHaveLength(1)
|
||||
expect(piece.types.value[0].name).toBe('Piece')
|
||||
})
|
||||
|
||||
it('invalidateEntityTypeCache only affects target category', async () => {
|
||||
mockListModelTypes
|
||||
.mockResolvedValueOnce({ items: [fakeItem('c1', 'Comp')], total: 1, offset: 0, limit: 200 })
|
||||
.mockResolvedValueOnce({ items: [fakeItem('p1', 'Piece')], total: 1, offset: 0, limit: 200 })
|
||||
|
||||
const comp = useEntityTypes({ category: 'COMPONENT', label: 'composant' })
|
||||
const piece = useEntityTypes({ category: 'PIECE', label: 'pièce' })
|
||||
await comp.loadTypes()
|
||||
await piece.loadTypes()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Invalidate only COMPONENT
|
||||
invalidateEntityTypeCache('COMPONENT')
|
||||
|
||||
// PIECE should still use cache
|
||||
await piece.loadTypes()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(2) // No extra call
|
||||
|
||||
// COMPONENT should refetch
|
||||
mockListModelTypes.mockResolvedValueOnce({ items: [fakeItem('c1', 'Refreshed')], total: 1, offset: 0, limit: 200 })
|
||||
await comp.loadTypes()
|
||||
expect(mockListModelTypes).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
83
frontend/tests/composables/useToast.test.ts
Normal file
83
frontend/tests/composables/useToast.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
|
||||
describe('useToast', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
const { clearAll } = useToast()
|
||||
clearAll()
|
||||
})
|
||||
|
||||
it('returns all expected functions', () => {
|
||||
const toast = useToast()
|
||||
expect(typeof toast.showToast).toBe('function')
|
||||
expect(typeof toast.showSuccess).toBe('function')
|
||||
expect(typeof toast.showError).toBe('function')
|
||||
expect(typeof toast.showWarning).toBe('function')
|
||||
expect(typeof toast.showInfo).toBe('function')
|
||||
expect(typeof toast.removeToast).toBe('function')
|
||||
expect(typeof toast.clearAll).toBe('function')
|
||||
})
|
||||
|
||||
it('adds a toast with correct properties', () => {
|
||||
const { showToast, toasts } = useToast()
|
||||
const id = showToast('Hello', 'info')
|
||||
expect(toasts.value).toHaveLength(1)
|
||||
expect(toasts.value[0].message).toBe('Hello')
|
||||
expect(toasts.value[0].type).toBe('info')
|
||||
expect(toasts.value[0].visible).toBe(true)
|
||||
expect(toasts.value[0].id).toBe(id)
|
||||
})
|
||||
|
||||
it('showSuccess creates a success toast', () => {
|
||||
const { showSuccess, toasts } = useToast()
|
||||
showSuccess('Saved!')
|
||||
expect(toasts.value[0].type).toBe('success')
|
||||
expect(toasts.value[0].message).toBe('Saved!')
|
||||
})
|
||||
|
||||
it('showError creates an error toast', () => {
|
||||
const { showError, toasts } = useToast()
|
||||
showError('Failed!')
|
||||
expect(toasts.value[0].type).toBe('error')
|
||||
})
|
||||
|
||||
it('showWarning creates a warning toast', () => {
|
||||
const { showWarning, toasts } = useToast()
|
||||
showWarning('Caution!')
|
||||
expect(toasts.value[0].type).toBe('warning')
|
||||
})
|
||||
|
||||
it('limits to MAX_TOASTS (3)', () => {
|
||||
const { showToast, toasts } = useToast()
|
||||
showToast('A', 'info')
|
||||
showToast('B', 'info')
|
||||
showToast('C', 'info')
|
||||
showToast('D', 'info')
|
||||
expect(toasts.value).toHaveLength(3)
|
||||
expect(toasts.value[0].message).toBe('B')
|
||||
expect(toasts.value[2].message).toBe('D')
|
||||
})
|
||||
|
||||
it('clearAll removes all toasts', () => {
|
||||
const { showToast, toasts, clearAll } = useToast()
|
||||
showToast('A', 'info')
|
||||
showToast('B', 'info')
|
||||
expect(toasts.value.length).toBeGreaterThan(0)
|
||||
clearAll()
|
||||
expect(toasts.value).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('shares state across calls (singleton)', () => {
|
||||
const a = useToast()
|
||||
const b = useToast()
|
||||
expect(a.toasts).toBe(b.toasts)
|
||||
})
|
||||
|
||||
it('removeToast sets visible to false', () => {
|
||||
const { showToast, toasts, removeToast } = useToast()
|
||||
const id = showToast('Test', 'info')
|
||||
removeToast(id)
|
||||
expect(toasts.value[0].visible).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user