diff --git a/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx
new file mode 100644
index 0000000000..356f813afc
--- /dev/null
+++ b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import { fireEvent, render, screen } from '@testing-library/react'
+import EditItem, { EditItemType } from './index'
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}))
+
+describe('AddAnnotationModal/EditItem', () => {
+ test('should render query inputs with user avatar and placeholder strings', () => {
+ render(
+ ,
+ )
+
+ expect(screen.getByText('appAnnotation.addModal.queryName')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.queryPlaceholder')).toBeInTheDocument()
+ expect(screen.getByText('Why?')).toBeInTheDocument()
+ })
+
+ test('should render answer name and placeholder text', () => {
+ render(
+ ,
+ )
+
+ expect(screen.getByText('appAnnotation.addModal.answerName')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.answerPlaceholder')).toBeInTheDocument()
+ expect(screen.getByDisplayValue('Existing answer')).toBeInTheDocument()
+ })
+
+ test('should propagate changes when answer content updates', () => {
+ const handleChange = jest.fn()
+ render(
+ ,
+ )
+
+ fireEvent.change(screen.getByPlaceholderText('appAnnotation.addModal.answerPlaceholder'), { target: { value: 'Because' } })
+ expect(handleChange).toHaveBeenCalledWith('Because')
+ })
+})
diff --git a/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx
new file mode 100644
index 0000000000..3103e3c96d
--- /dev/null
+++ b/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx
@@ -0,0 +1,155 @@
+import React from 'react'
+import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import AddAnnotationModal from './index'
+import { useProviderContext } from '@/context/provider-context'
+
+jest.mock('@/context/provider-context', () => ({
+ useProviderContext: jest.fn(),
+}))
+
+const mockToastNotify = jest.fn()
+jest.mock('@/app/components/base/toast', () => ({
+ __esModule: true,
+ default: {
+ notify: jest.fn(args => mockToastNotify(args)),
+ },
+}))
+
+jest.mock('@/app/components/billing/annotation-full', () => () =>
)
+
+const mockUseProviderContext = useProviderContext as jest.Mock
+
+const getProviderContext = ({ usage = 0, total = 10, enableBilling = false } = {}) => ({
+ plan: {
+ usage: { annotatedResponse: usage },
+ total: { annotatedResponse: total },
+ },
+ enableBilling,
+})
+
+describe('AddAnnotationModal', () => {
+ const baseProps = {
+ isShow: true,
+ onHide: jest.fn(),
+ onAdd: jest.fn(),
+ }
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ mockUseProviderContext.mockReturnValue(getProviderContext())
+ })
+
+ const typeQuestion = (value: string) => {
+ fireEvent.change(screen.getByPlaceholderText('appAnnotation.addModal.queryPlaceholder'), {
+ target: { value },
+ })
+ }
+
+ const typeAnswer = (value: string) => {
+ fireEvent.change(screen.getByPlaceholderText('appAnnotation.addModal.answerPlaceholder'), {
+ target: { value },
+ })
+ }
+
+ test('should render modal title when drawer is visible', () => {
+ render()
+
+ expect(screen.getByText('appAnnotation.addModal.title')).toBeInTheDocument()
+ })
+
+ test('should capture query input text when typing', () => {
+ render()
+ typeQuestion('Sample question')
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.queryPlaceholder')).toHaveValue('Sample question')
+ })
+
+ test('should capture answer input text when typing', () => {
+ render()
+ typeAnswer('Sample answer')
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.answerPlaceholder')).toHaveValue('Sample answer')
+ })
+
+ test('should show annotation full notice and disable submit when quota exceeded', () => {
+ mockUseProviderContext.mockReturnValue(getProviderContext({ usage: 10, total: 10, enableBilling: true }))
+ render()
+
+ expect(screen.getByTestId('annotation-full')).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'common.operation.add' })).toBeDisabled()
+ })
+
+ test('should call onAdd with form values when create next enabled', async () => {
+ const onAdd = jest.fn().mockResolvedValue(undefined)
+ render()
+
+ typeQuestion('Question value')
+ typeAnswer('Answer value')
+ fireEvent.click(screen.getByTestId('checkbox-create-next-checkbox'))
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' }))
+ })
+
+ expect(onAdd).toHaveBeenCalledWith({ question: 'Question value', answer: 'Answer value' })
+ })
+
+ test('should reset fields after saving when create next enabled', async () => {
+ const onAdd = jest.fn().mockResolvedValue(undefined)
+ render()
+
+ typeQuestion('Question value')
+ typeAnswer('Answer value')
+ const createNextToggle = screen.getByText('appAnnotation.addModal.createNext').previousElementSibling as HTMLElement
+ fireEvent.click(createNextToggle)
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' }))
+ })
+
+ await waitFor(() => {
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.queryPlaceholder')).toHaveValue('')
+ expect(screen.getByPlaceholderText('appAnnotation.addModal.answerPlaceholder')).toHaveValue('')
+ })
+ })
+
+ test('should show toast when validation fails for missing question', () => {
+ render()
+
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' }))
+ expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
+ type: 'error',
+ message: 'appAnnotation.errorMessage.queryRequired',
+ }))
+ })
+
+ test('should show toast when validation fails for missing answer', () => {
+ render()
+ typeQuestion('Filled question')
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' }))
+
+ expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
+ type: 'error',
+ message: 'appAnnotation.errorMessage.answerRequired',
+ }))
+ })
+
+ test('should close modal when save completes and create next unchecked', async () => {
+ const onAdd = jest.fn().mockResolvedValue(undefined)
+ render()
+
+ typeQuestion('Q')
+ typeAnswer('A')
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' }))
+ })
+
+ expect(baseProps.onHide).toHaveBeenCalled()
+ })
+
+ test('should allow cancel button to close the drawer', () => {
+ render()
+
+ fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
+ expect(baseProps.onHide).toHaveBeenCalled()
+ })
+})
diff --git a/web/app/components/app/annotation/add-annotation-modal/index.tsx b/web/app/components/app/annotation/add-annotation-modal/index.tsx
index 274a57adf1..0ae4439531 100644
--- a/web/app/components/app/annotation/add-annotation-modal/index.tsx
+++ b/web/app/components/app/annotation/add-annotation-modal/index.tsx
@@ -101,7 +101,7 @@ const AddAnnotationModal: FC = ({
-
setIsCreateNext(!isCreateNext)} />
+ setIsCreateNext(!isCreateNext)} />
{t('appAnnotation.addModal.createNext')}