feat: initial setup with WhatsApp CRM apps and sync services
This commit is contained in:
253
apps/whatsapp-crm/tests/integration.test.ts
Normal file
253
apps/whatsapp-crm/tests/integration.test.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import { initDB, createContact, createTask, createMessage, getContactById, getTasksByContactId, getMessagesByContactId, updateContact, updateTask, closeDB } from '@/lib/db'
|
||||
import { STAGES } from '@/lib/types'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initDB()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
closeDB()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const db = require('@/lib/db').getDB()
|
||||
db.run('DELETE FROM tasks')
|
||||
db.run('DELETE FROM messages')
|
||||
db.run('DELETE FROM contacts')
|
||||
})
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
describe('10.1 Full Pipeline Tests', () => {
|
||||
it('test_full_contact_lifecycle', async () => {
|
||||
const contact = createContact({ name: 'Test User', phone: '+5511999999991' })
|
||||
expect(contact.id).toBeGreaterThan(0)
|
||||
|
||||
const updated = updateContact(contact.id, { stage: STAGES.DECIDINDO })
|
||||
expect(updated?.stage).toBe(STAGES.DECIDINDO)
|
||||
|
||||
const fetched = getContactById(contact.id)
|
||||
expect(fetched?.name).toBe('Test User')
|
||||
expect(fetched?.stage).toBe(STAGES.DECIDINDO)
|
||||
})
|
||||
|
||||
it('test_contact_with_tasks_lifecycle', async () => {
|
||||
const contact = createContact({ name: 'Task User', phone: '+5511999999992' })
|
||||
|
||||
const task1 = createTask({ contact_id: contact.id, title: 'Task 1', due_date: '2026-04-10' })
|
||||
const task2 = createTask({ contact_id: contact.id, title: 'Task 2', due_date: '2026-04-15' })
|
||||
|
||||
const tasks = getTasksByContactId(contact.id)
|
||||
expect(tasks).toHaveLength(2)
|
||||
|
||||
updateTask(task1.id, { status: 'completed' })
|
||||
|
||||
const allTasks = getTasksByContactId(contact.id)
|
||||
const pending = allTasks.filter(t => t.status === 'pending')
|
||||
expect(pending).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('test_contact_with_messages_lifecycle', async () => {
|
||||
const contact = createContact({ name: 'Message User', phone: '+5511999999993' })
|
||||
|
||||
const msg1 = createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'msg-1',
|
||||
body: 'Hello',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704067200,
|
||||
has_media: false,
|
||||
})
|
||||
|
||||
const msg2 = createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'msg-2',
|
||||
body: 'How are you?',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704070800,
|
||||
has_media: false,
|
||||
})
|
||||
|
||||
const messages = getMessagesByContactId(contact.id)
|
||||
expect(messages).toHaveLength(2)
|
||||
expect(messages[0].body).toBe('How are you?')
|
||||
})
|
||||
|
||||
it('test_duplicate_message_filtered', async () => {
|
||||
const contact = createContact({ name: 'Dupe User', phone: '+5511999999994' })
|
||||
|
||||
createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'same-id',
|
||||
body: 'First message',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704067200,
|
||||
})
|
||||
|
||||
const duplicate = createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'same-id',
|
||||
body: 'Duplicate message',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704070800,
|
||||
})
|
||||
|
||||
expect(duplicate).toBeNull()
|
||||
|
||||
const messages = getMessagesByContactId(contact.id)
|
||||
expect(messages).toHaveLength(1)
|
||||
expect(messages[0].body).toBe('First message')
|
||||
})
|
||||
})
|
||||
|
||||
describe('10.2 Multi-Feature Tests', () => {
|
||||
it('test_search_finds_contact_with_tasks', async () => {
|
||||
createContact({ name: 'Searchable Contact', phone: '+5511999999991' })
|
||||
const contact2 = createContact({ name: 'Other Contact', phone: '+5511999999992' })
|
||||
|
||||
createTask({ contact_id: contact2.id, title: 'Important Task', due_date: '2026-04-10' })
|
||||
|
||||
const { searchContacts } = require('@/lib/db')
|
||||
const results = searchContacts('Searchable')
|
||||
expect(results).toHaveLength(1)
|
||||
expect(results[0].name).toBe('Searchable Contact')
|
||||
})
|
||||
|
||||
it('test_filter_stage_preserves_tasks', async () => {
|
||||
const contact1 = createContact({ name: 'Lead 1', phone: '+5511999999991' })
|
||||
const contact2 = createContact({ name: 'Lead 2', phone: '+5511999999992' })
|
||||
|
||||
updateContact(contact1.id, { stage: STAGES.DECIDINDO })
|
||||
|
||||
createTask({ contact_id: contact1.id, title: 'Task for Decidindo', due_date: '2026-04-10' })
|
||||
createTask({ contact_id: contact2.id, title: 'Task for Leads', due_date: '2026-04-10' })
|
||||
|
||||
const { getContactsByStage } = require('@/lib/db')
|
||||
const leads = getContactsByStage(STAGES.LEADS_DE_ENTRADA)
|
||||
const decidindo = getContactsByStage(STAGES.DECIDINDO)
|
||||
|
||||
expect(leads).toHaveLength(1)
|
||||
expect(decidindo).toHaveLength(1)
|
||||
|
||||
const decidindoTasks = getTasksByContactId(decidindo[0].id)
|
||||
expect(decidindoTasks).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('test_payment_status_update_preserves_all_data', async () => {
|
||||
const contact = createContact({ name: 'Payment User', phone: '+5511999999991', notes: 'Initial note' })
|
||||
|
||||
createTask({ contact_id: contact.id, title: 'Task 1', due_date: '2026-04-10' })
|
||||
createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'msg-1',
|
||||
body: 'Message 1',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704067200,
|
||||
})
|
||||
|
||||
updateContact(contact.id, { payment_status: 'paid', notes: 'Updated note' })
|
||||
|
||||
const updated = getContactById(contact.id)
|
||||
expect(updated?.payment_status).toBe('paid')
|
||||
expect(updated?.notes).toBe('Updated note')
|
||||
|
||||
const tasks = getTasksByContactId(contact.id)
|
||||
expect(tasks).toHaveLength(1)
|
||||
|
||||
const messages = getMessagesByContactId(contact.id)
|
||||
expect(messages).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('10.3 Performance Tests', () => {
|
||||
it('test_bulk_contact_creation', async () => {
|
||||
const startTime = Date.now()
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
createContact({ name: `Contact ${i}`, phone: `+551199999${String(i).padStart(4, '0')}` })
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - startTime
|
||||
expect(elapsed).toBeLessThan(2000)
|
||||
})
|
||||
|
||||
it('test_bulk_task_creation', async () => {
|
||||
const contact = createContact({ name: 'Bulk User', phone: '+5511999999991' })
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
createTask({ contact_id: contact.id, title: `Task ${i}`, due_date: '2026-04-10' })
|
||||
}
|
||||
|
||||
const tasks = getTasksByContactId(contact.id)
|
||||
expect(tasks).toHaveLength(20)
|
||||
})
|
||||
|
||||
it('test_bulk_message_creation', async () => {
|
||||
const contact = createContact({ name: 'Bulk Msg User', phone: '+5511999999991' })
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: `msg-${i}`,
|
||||
body: `Message ${i}`,
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704067200 + i * 3600,
|
||||
})
|
||||
}
|
||||
|
||||
const messages = getMessagesByContactId(contact.id)
|
||||
expect(messages).toHaveLength(20)
|
||||
})
|
||||
})
|
||||
|
||||
describe('10.4 Edge Cases', () => {
|
||||
it('test_delete_contact_cascades_tasks_and_messages', async () => {
|
||||
const contact = createContact({ name: 'To Delete', phone: '+5511999999991' })
|
||||
|
||||
createTask({ contact_id: contact.id, title: 'Task', due_date: '2026-04-10' })
|
||||
createMessage({
|
||||
contact_id: contact.id,
|
||||
external_id: 'msg-1',
|
||||
body: 'Message',
|
||||
from_phone: contact.phone,
|
||||
timestamp: 1704067200,
|
||||
})
|
||||
|
||||
const { deleteContact } = require('@/lib/db')
|
||||
deleteContact(contact.id)
|
||||
|
||||
const tasks = getTasksByContactId(contact.id)
|
||||
const messages = getMessagesByContactId(contact.id)
|
||||
|
||||
expect(tasks).toHaveLength(0)
|
||||
expect(messages).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('test_stage_transitions_all_stages', async () => {
|
||||
const contact = createContact({ name: 'Stage User', phone: '+5511999999991' })
|
||||
|
||||
expect(contact.stage).toBe(STAGES.LEADS_DE_ENTRADA)
|
||||
|
||||
let updated = updateContact(contact.id, { stage: STAGES.DECIDINDO })
|
||||
expect(updated?.stage).toBe(STAGES.DECIDINDO)
|
||||
|
||||
updated = updateContact(contact.id, { stage: STAGES.DISCUSSAO_DE_CONTRATO })
|
||||
expect(updated?.stage).toBe(STAGES.DISCUSSAO_DE_CONTRATO)
|
||||
|
||||
updated = updateContact(contact.id, { stage: STAGES.DECISAO_FINAL })
|
||||
expect(updated?.stage).toBe(STAGES.DECISAO_FINAL)
|
||||
})
|
||||
|
||||
it('test_task_completion_updates_correctly', async () => {
|
||||
const contact = createContact({ name: 'Task User', phone: '+5511999999991' })
|
||||
const task = createTask({ contact_id: contact.id, title: 'To Complete', due_date: '2026-04-10' })
|
||||
|
||||
const completed = updateTask(task.id, { status: 'completed' })
|
||||
expect(completed?.status).toBe('completed')
|
||||
expect(completed?.completed_at).not.toBeNull()
|
||||
|
||||
const reOpened = updateTask(task.id, { status: 'pending' })
|
||||
expect(reOpened?.status).toBe('pending')
|
||||
expect(reOpened?.completed_at).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
183
apps/whatsapp-crm/tests/kanban.test.ts
Normal file
183
apps/whatsapp-crm/tests/kanban.test.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { STAGES, Stage, Contact } from '@/lib/types'
|
||||
|
||||
describe('Kanban Board', () => {
|
||||
describe('3.1 Column Component Tests', () => {
|
||||
it('test_renders_four_columns', () => {
|
||||
const { STAGE_LIST } = require('@/lib/types')
|
||||
expect(STAGE_LIST).toHaveLength(4)
|
||||
expect(STAGE_LIST).toContain('LEADS DE ENTRADA')
|
||||
expect(STAGE_LIST).toContain('DECIDINDO')
|
||||
expect(STAGE_LIST).toContain('DISCUSSAO DE CONTRATO')
|
||||
expect(STAGE_LIST).toContain('DECISAO FINAL')
|
||||
})
|
||||
|
||||
it('test_column_displays_contact_count', () => {
|
||||
const contacts: Contact[] = [
|
||||
{ id: 1, name: 'Contact 1', phone: '+5511999999991', stage: STAGES.LEADS_DE_ENTRADA, notes: null, schedule_date: null, follow_up_date: null, payment_status: 'pending', created_at: '', updated_at: '' },
|
||||
{ id: 2, name: 'Contact 2', phone: '+5511999999992', stage: STAGES.LEADS_DE_ENTRADA, notes: null, schedule_date: null, follow_up_date: null, payment_status: 'pending', created_at: '', updated_at: '' },
|
||||
]
|
||||
|
||||
const leadsContacts = contacts.filter(c => c.stage === STAGES.LEADS_DE_ENTRADA)
|
||||
expect(leadsContacts.length).toBe(2)
|
||||
})
|
||||
|
||||
it('test_empty_column_shows_placeholder', () => {
|
||||
const contacts: Contact[] = []
|
||||
const leadsContacts = contacts.filter(c => c.stage === STAGES.LEADS_DE_ENTRADA)
|
||||
|
||||
const shouldShowEmpty = leadsContacts.length === 0
|
||||
expect(shouldShowEmpty).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('3.2 Contact Card Tests', () => {
|
||||
it('test_card_displays_contact_name', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: null,
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
}
|
||||
|
||||
expect(contact.name).toBe('John Doe')
|
||||
})
|
||||
|
||||
it('test_card_displays_formatted_phone', () => {
|
||||
const formatPhone = (phone: string) => {
|
||||
const cleaned = phone.replace(/\D/g, '')
|
||||
if (cleaned.length === 13) {
|
||||
return `+${cleaned.slice(0, 2)} (${cleaned.slice(3, 5)}) ${cleaned.slice(5, 10)}-${cleaned.slice(10)}`
|
||||
}
|
||||
if (cleaned.length === 12) {
|
||||
return `+${cleaned.slice(0, 2)} (${cleaned.slice(2, 4)}) ${cleaned.slice(4, 9)}-${cleaned.slice(9)}`
|
||||
}
|
||||
if (cleaned.length === 11) {
|
||||
return `(${cleaned.slice(0, 2)}) ${cleaned.slice(2, 7)}-${cleaned.slice(7)}`
|
||||
}
|
||||
return phone
|
||||
}
|
||||
|
||||
const formatted = formatPhone('+5511999999999')
|
||||
expect(formatted).toContain('+55')
|
||||
expect(formatted).toContain('99999')
|
||||
})
|
||||
|
||||
it('test_card_displays_message_preview', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: 'This is a very long note that should be truncated when displayed in the card preview area',
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
}
|
||||
|
||||
const preview = contact.notes ? contact.notes.substring(0, 50) + (contact.notes.length > 50 ? '...' : '') : ''
|
||||
expect(preview.length).toBe(53)
|
||||
expect(preview).toContain('...')
|
||||
})
|
||||
|
||||
it('test_card_displays_task_count_badge', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: null,
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
taskCount: 3
|
||||
}
|
||||
|
||||
expect(contact.taskCount).toBe(3)
|
||||
expect(contact.taskCount! > 0).toBe(true)
|
||||
})
|
||||
|
||||
it('test_card_displays_overdue_indicator', () => {
|
||||
const pastDate = '2020-01-01'
|
||||
const futureDate = '2030-01-01'
|
||||
|
||||
const isPastOverdue = pastDate && new Date(pastDate) < new Date()
|
||||
const isFutureOverdue = futureDate && new Date(futureDate) < new Date()
|
||||
|
||||
expect(isPastOverdue).toBe(true)
|
||||
expect(isFutureOverdue).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('3.3 Drag and Drop Tests', () => {
|
||||
it('test_drag_card_to_different_stage', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: null,
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
}
|
||||
|
||||
const newStage = STAGES.DECIDINDO
|
||||
const updatedContact = { ...contact, stage: newStage }
|
||||
|
||||
expect(updatedContact.stage).toBe(STAGES.DECIDINDO)
|
||||
expect(updatedContact.stage).not.toBe(contact.stage)
|
||||
})
|
||||
|
||||
it('test_cannot_drag_to_same_column', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: null,
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
}
|
||||
|
||||
const sameStage = STAGES.LEADS_DE_ENTRADA
|
||||
const updatedContact = { ...contact, stage: sameStage }
|
||||
|
||||
expect(updatedContact.stage).toBe(contact.stage)
|
||||
expect(updatedContact.stage).not.toBe(STAGES.DECIDINDO)
|
||||
})
|
||||
|
||||
it('test_drag_cancelled_returns_card', () => {
|
||||
const contact: Contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: null,
|
||||
schedule_date: null,
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
}
|
||||
|
||||
const originalContact = { ...contact }
|
||||
|
||||
expect(contact.stage).toBe(originalContact.stage)
|
||||
})
|
||||
})
|
||||
})
|
||||
123
apps/whatsapp-crm/tests/modal.test.ts
Normal file
123
apps/whatsapp-crm/tests/modal.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Stage, STAGES } from '@/lib/types'
|
||||
|
||||
describe('Contact Detail View', () => {
|
||||
describe('4.1 Modal Tests', () => {
|
||||
it('test_modal_opens_on_card_click', () => {
|
||||
const isModalOpen = true
|
||||
expect(isModalOpen).toBe(true)
|
||||
})
|
||||
|
||||
it('test_modal_displays_all_fields', () => {
|
||||
const contact = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
phone: '+5511999999999',
|
||||
stage: STAGES.LEADS_DE_ENTRADA,
|
||||
notes: 'Test note',
|
||||
schedule_date: '2026-04-10',
|
||||
follow_up_date: null,
|
||||
payment_status: 'pending' as const,
|
||||
created_at: '2026-04-01',
|
||||
updated_at: '2026-04-01',
|
||||
}
|
||||
|
||||
expect(contact.name).toBeDefined()
|
||||
expect(contact.phone).toBeDefined()
|
||||
expect(contact.stage).toBeDefined()
|
||||
expect(contact.notes).toBeDefined()
|
||||
expect(contact.payment_status).toBeDefined()
|
||||
})
|
||||
|
||||
it('test_modal_close_on_overlay_click', () => {
|
||||
const handleClose = jest.fn()
|
||||
const event = { target: { classList: { contains: (cls: string) => cls === 'modal-overlay' } } }
|
||||
|
||||
if (event.target.classList.contains('modal-overlay')) {
|
||||
handleClose()
|
||||
}
|
||||
|
||||
expect(handleClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('test_modal_close_on_escape_key', () => {
|
||||
const handleClose = jest.fn()
|
||||
const event = { key: 'Escape' }
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
handleClose()
|
||||
}
|
||||
|
||||
expect(handleClose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('4.2 Message History Tests', () => {
|
||||
it('test_displays_message_history_sorted_by_date', () => {
|
||||
const messages = [
|
||||
{ id: 1, timestamp: 1000, body: 'First' },
|
||||
{ id: 2, timestamp: 2000, body: 'Second' },
|
||||
{ id: 3, timestamp: 500, body: 'Third' },
|
||||
]
|
||||
|
||||
const sorted = [...messages].sort((a, b) => b.timestamp - a.timestamp)
|
||||
expect(sorted[0].body).toBe('Second')
|
||||
expect(sorted[1].body).toBe('First')
|
||||
expect(sorted[2].body).toBe('Third')
|
||||
})
|
||||
|
||||
it('test_message_shows_content_and_time', () => {
|
||||
const message = {
|
||||
id: 1,
|
||||
timestamp: 1704067200,
|
||||
body: 'Hello world',
|
||||
}
|
||||
|
||||
const date = new Date(message.timestamp * 1000)
|
||||
expect(message.body).toBe('Hello world')
|
||||
expect(date.getTime()).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('test_pagination_loads_more_messages', () => {
|
||||
const allMessages = Array.from({ length: 100 }, (_, i) => ({ id: i, body: `Message ${i}` }))
|
||||
const pageSize = 20
|
||||
let currentPage = 0
|
||||
|
||||
const getPage = (page: number) => {
|
||||
const start = page * pageSize
|
||||
return allMessages.slice(start, start + pageSize)
|
||||
}
|
||||
|
||||
const firstPage = getPage(currentPage)
|
||||
expect(firstPage).toHaveLength(pageSize)
|
||||
|
||||
currentPage++
|
||||
const secondPage = getPage(currentPage)
|
||||
expect(secondPage).toHaveLength(pageSize)
|
||||
expect(secondPage[0].body).toBe('Message 20')
|
||||
})
|
||||
})
|
||||
|
||||
describe('4.3 Notes and Fields Tests', () => {
|
||||
it('test_add_note_to_contact', () => {
|
||||
let notes = ''
|
||||
|
||||
const addNote = (newNote: string) => {
|
||||
notes = newNote
|
||||
}
|
||||
|
||||
addNote('This is a new note')
|
||||
expect(notes).toBe('This is a new note')
|
||||
})
|
||||
|
||||
it('test_update_payment_status', () => {
|
||||
let paymentStatus: 'pending' | 'paid' = 'pending'
|
||||
|
||||
const updateStatus = (status: 'pending' | 'paid') => {
|
||||
paymentStatus = status
|
||||
}
|
||||
|
||||
updateStatus('paid')
|
||||
expect(paymentStatus).toBe('paid')
|
||||
})
|
||||
})
|
||||
})
|
||||
22
apps/whatsapp-crm/tests/project.test.ts
Normal file
22
apps/whatsapp-crm/tests/project.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
describe('Project Setup', () => {
|
||||
it('should have types defined', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
|
||||
it('should have STAGES constant', () => {
|
||||
const { STAGES, STAGE_LIST } = require('@/lib/types')
|
||||
expect(STAGES.LEADS_DE_ENTRADA).toBe('LEADS DE ENTRADA')
|
||||
expect(STAGES.DECIDINDO).toBe('DECIDINDO')
|
||||
expect(STAGES.DISCUSSAO_DE_CONTRATO).toBe('DISCUSSAO DE CONTRATO')
|
||||
expect(STAGES.DECISAO_FINAL).toBe('DECISAO FINAL')
|
||||
expect(STAGE_LIST).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('should have stage list exported', () => {
|
||||
const { STAGE_LIST } = require('@/lib/types')
|
||||
expect(STAGE_LIST).toContain('LEADS DE ENTRADA')
|
||||
expect(STAGE_LIST).toContain('DECIDINDO')
|
||||
expect(STAGE_LIST).toContain('DISCUSSAO DE CONTRATO')
|
||||
expect(STAGE_LIST).toContain('DECISAO FINAL')
|
||||
})
|
||||
})
|
||||
103
apps/whatsapp-crm/tests/search-filter.test.ts
Normal file
103
apps/whatsapp-crm/tests/search-filter.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { initDB, createContact, searchContacts, getContactsByStage, getContactsByPaymentStatus, closeDB } from '@/lib/db'
|
||||
import { STAGES } from '@/lib/types'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initDB()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
closeDB()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const db = require('@/lib/db').getDB()
|
||||
db.run('DELETE FROM tasks')
|
||||
db.run('DELETE FROM messages')
|
||||
db.run('DELETE FROM contacts')
|
||||
})
|
||||
|
||||
describe('Search & Filter', () => {
|
||||
describe('6.1 Search Tests', () => {
|
||||
it('test_search_contacts_by_name', async () => {
|
||||
createContact({ name: 'John Doe', phone: '+5511999999991' })
|
||||
createContact({ name: 'Jane Smith', phone: '+5511999999992' })
|
||||
createContact({ name: 'Alice Brown', phone: '+5511999999993' })
|
||||
|
||||
const results = searchContacts('John')
|
||||
|
||||
expect(results).toHaveLength(1)
|
||||
expect(results[0].name).toBe('John Doe')
|
||||
})
|
||||
|
||||
it('test_search_contacts_by_phone', async () => {
|
||||
createContact({ name: 'Contact One', phone: '+5511999999991' })
|
||||
createContact({ name: 'Contact Two', phone: '+5511888888888' })
|
||||
|
||||
const results = searchContacts('19999')
|
||||
|
||||
expect(results).toHaveLength(1)
|
||||
expect(results[0].phone).toBe('+5511999999991')
|
||||
})
|
||||
|
||||
it('test_search_returns_empty_for_no_match', async () => {
|
||||
createContact({ name: 'John Doe', phone: '+5511999999991' })
|
||||
|
||||
const results = searchContacts('nonexistent')
|
||||
|
||||
expect(results).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('6.2 Filter Tests', () => {
|
||||
it('test_filter_by_stage', async () => {
|
||||
const c1 = createContact({ name: 'Lead 1', phone: '+5511999999991' })
|
||||
const c2 = createContact({ name: 'Lead 2', phone: '+5511999999992' })
|
||||
const c3 = createContact({ name: 'Deciding', phone: '+5511999999993' })
|
||||
|
||||
const { updateContact } = require('@/lib/db')
|
||||
updateContact(c1.id, { stage: STAGES.LEADS_DE_ENTRADA })
|
||||
updateContact(c2.id, { stage: STAGES.LEADS_DE_ENTRADA })
|
||||
updateContact(c3.id, { stage: STAGES.DECIDINDO })
|
||||
|
||||
const leads = getContactsByStage(STAGES.LEADS_DE_ENTRADA)
|
||||
const decidindo = getContactsByStage(STAGES.DECIDINDO)
|
||||
|
||||
expect(leads).toHaveLength(2)
|
||||
expect(decidindo).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('test_filter_by_payment_status', async () => {
|
||||
const c1 = createContact({ name: 'Paid Contact', phone: '+5511999999991' })
|
||||
const c2 = createContact({ name: 'Pending Contact', phone: '+5511999999992' })
|
||||
|
||||
const { updateContact } = require('@/lib/db')
|
||||
updateContact(c1.id, { payment_status: 'paid' })
|
||||
updateContact(c2.id, { payment_status: 'pending' })
|
||||
|
||||
const paid = getContactsByPaymentStatus('paid')
|
||||
const pending = getContactsByPaymentStatus('pending')
|
||||
|
||||
expect(paid).toHaveLength(1)
|
||||
expect(pending).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('test_filter_combined_stage_and_payment', async () => {
|
||||
const c1 = createContact({ name: 'Lead Paid', phone: '+5511999999991' })
|
||||
const c2 = createContact({ name: 'Lead Pending', phone: '+5511999999992' })
|
||||
const c3 = createContact({ name: 'Deciding Paid', phone: '+5511999999993' })
|
||||
|
||||
const { updateContact } = require('@/lib/db')
|
||||
updateContact(c1.id, { stage: STAGES.LEADS_DE_ENTRADA, payment_status: 'paid' })
|
||||
updateContact(c2.id, { stage: STAGES.LEADS_DE_ENTRADA, payment_status: 'pending' })
|
||||
updateContact(c3.id, { stage: STAGES.DECIDINDO, payment_status: 'paid' })
|
||||
|
||||
const leadsByStage = getContactsByStage(STAGES.LEADS_DE_ENTRADA)
|
||||
const paidByStatus = getContactsByPaymentStatus('paid')
|
||||
const combined = leadsByStage.filter(c => c.payment_status === 'paid')
|
||||
|
||||
expect(leadsByStage).toHaveLength(2)
|
||||
expect(paidByStatus).toHaveLength(2)
|
||||
expect(combined).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
19
apps/whatsapp-crm/tests/setup.ts
Normal file
19
apps/whatsapp-crm/tests/setup.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import '@testing-library/jest-dom'
|
||||
import { cleanup } from '@testing-library/react'
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
const mockRouter = {
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
back: jest.fn(),
|
||||
}
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => mockRouter,
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
usePathname: () => '/',
|
||||
}))
|
||||
147
apps/whatsapp-crm/tests/tasks.test.ts
Normal file
147
apps/whatsapp-crm/tests/tasks.test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { initDB, createContact, createTask, getTaskById, getTasksByContactId, getPendingTasksByContactId, getOverdueTasks, updateTask, deleteTask, closeDB } from '@/lib/db'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initDB()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
closeDB()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const db = require('@/lib/db').getDB()
|
||||
db.run('DELETE FROM tasks')
|
||||
db.run('DELETE FROM messages')
|
||||
db.run('DELETE FROM contacts')
|
||||
})
|
||||
|
||||
describe('Task Management', () => {
|
||||
describe('5.1 Task Creation Tests', () => {
|
||||
it('test_create_task_with_required_fields', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999999' })
|
||||
|
||||
const task = createTask({
|
||||
contact_id: contact.id,
|
||||
title: 'Follow up call',
|
||||
due_date: '2026-04-10',
|
||||
})
|
||||
|
||||
expect(task).toBeDefined()
|
||||
expect(task.id).toBeGreaterThan(0)
|
||||
expect(task.contact_id).toBe(contact.id)
|
||||
expect(task.title).toBe('Follow up call')
|
||||
expect(task.due_date).toBe('2026-04-10')
|
||||
expect(task.status).toBe('pending')
|
||||
expect(task.completed_at).toBeNull()
|
||||
})
|
||||
|
||||
it('test_create_task_missing_due_date', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999998' })
|
||||
|
||||
expect(() => {
|
||||
createTask({
|
||||
contact_id: contact.id,
|
||||
title: 'Follow up call',
|
||||
due_date: '',
|
||||
})
|
||||
}).toThrow('Title and due date are required')
|
||||
})
|
||||
|
||||
it('test_create_task_missing_title', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999997' })
|
||||
|
||||
expect(() => {
|
||||
createTask({
|
||||
contact_id: contact.id,
|
||||
title: '',
|
||||
due_date: '2026-04-10',
|
||||
})
|
||||
}).toThrow('Title and due date are required')
|
||||
})
|
||||
})
|
||||
|
||||
describe('5.2 Task Updates Tests', () => {
|
||||
it('test_mark_task_completed', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999996' })
|
||||
const task = createTask({
|
||||
contact_id: contact.id,
|
||||
title: 'Follow up call',
|
||||
due_date: '2026-04-10',
|
||||
})
|
||||
|
||||
const updated = updateTask(task.id, { status: 'completed' })
|
||||
|
||||
expect(updated).toBeDefined()
|
||||
expect(updated?.status).toBe('completed')
|
||||
expect(updated?.completed_at).not.toBeNull()
|
||||
})
|
||||
|
||||
it('test_update_task_due_date', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999995' })
|
||||
const task = createTask({
|
||||
contact_id: contact.id,
|
||||
title: 'Follow up call',
|
||||
due_date: '2026-04-10',
|
||||
})
|
||||
|
||||
const updated = updateTask(task.id, { due_date: '2026-04-15' })
|
||||
|
||||
expect(updated).toBeDefined()
|
||||
expect(updated?.due_date).toBe('2026-04-15')
|
||||
})
|
||||
})
|
||||
|
||||
describe('5.3 Task Queries Tests', () => {
|
||||
it('test_list_pending_tasks_for_contact', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999994' })
|
||||
|
||||
createTask({ contact_id: contact.id, title: 'Task 1', due_date: '2026-04-10' })
|
||||
createTask({ contact_id: contact.id, title: 'Task 2', due_date: '2026-04-12' })
|
||||
|
||||
const completedTask = createTask({ contact_id: contact.id, title: 'Task 3', due_date: '2026-04-08' })
|
||||
updateTask(completedTask.id, { status: 'completed' })
|
||||
|
||||
const pendingTasks = getPendingTasksByContactId(contact.id)
|
||||
|
||||
expect(pendingTasks).toHaveLength(2)
|
||||
expect(pendingTasks.every(t => t.status === 'pending')).toBe(true)
|
||||
})
|
||||
|
||||
it('test_list_overdue_tasks', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999993' })
|
||||
|
||||
createTask({ contact_id: contact.id, title: 'Overdue Task', due_date: '2020-01-01' })
|
||||
createTask({ contact_id: contact.id, title: 'Future Task', due_date: '2030-01-01' })
|
||||
|
||||
const overdueTasks = getOverdueTasks()
|
||||
|
||||
expect(overdueTasks.length).toBeGreaterThan(0)
|
||||
expect(overdueTasks.every(t => t.status === 'pending' && t.due_date < new Date().toISOString().split('T')[0])).toBe(true)
|
||||
})
|
||||
|
||||
it('test_create_task_without_contact', async () => {
|
||||
expect(() => {
|
||||
createTask({
|
||||
contact_id: 99999,
|
||||
title: 'Follow up call',
|
||||
due_date: '2026-04-10',
|
||||
})
|
||||
}).toThrow('Contact not found')
|
||||
})
|
||||
|
||||
it('test_get_task_by_id', async () => {
|
||||
const contact = createContact({ name: 'Test Contact', phone: '+5511999999992' })
|
||||
const task = createTask({
|
||||
contact_id: contact.id,
|
||||
title: 'Test Task',
|
||||
due_date: '2026-04-15',
|
||||
})
|
||||
|
||||
const found = getTaskById(task.id)
|
||||
|
||||
expect(found).toBeDefined()
|
||||
expect(found?.id).toBe(task.id)
|
||||
expect(found?.title).toBe('Test Task')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user