Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ let listPublicArticlesForCategory: typeof import('../help-center.article.query')

beforeEach(async () => {
vi.clearAllMocks()
mockArticleFindFirst.mockReset().mockResolvedValue(null)
mockArticleFindMany.mockReset().mockResolvedValue([])
mockCategoryFindMany.mockReset().mockResolvedValue([])

const mod = await import('../help-center.article.query')
listArticles = mod.listArticles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function createUpdateChain() {
}

const mockCategoryFindFirst = vi.fn()
const mockCategoryFindMany = vi.fn()
const mockArticleFindFirst = vi.fn()
const mockFeedbackFindFirst = vi.fn()
const mockPrincipalFindFirst = vi.fn()
Expand All @@ -48,6 +49,7 @@ vi.mock('@/lib/server/db', () => ({
query: {
helpCenterCategories: {
findFirst: (...args: unknown[]) => mockCategoryFindFirst(...args),
findMany: (...args: unknown[]) => mockCategoryFindMany(...args),
},
helpCenterArticles: {
findFirst: (...args: unknown[]) => mockArticleFindFirst(...args),
Expand Down Expand Up @@ -136,6 +138,11 @@ let recordArticleFeedback: typeof import('../help-center.article.service').recor

beforeEach(async () => {
vi.clearAllMocks()
mockCategoryFindFirst.mockReset().mockResolvedValue(null)
mockCategoryFindMany.mockReset().mockResolvedValue([])
mockArticleFindFirst.mockReset().mockResolvedValue(null)
mockFeedbackFindFirst.mockReset().mockResolvedValue(null)
mockPrincipalFindFirst.mockReset().mockResolvedValue(null)
insertValuesCalls.length = 0
updateSetCalls.length = 0
updateWhereCalls.length = 0
Expand Down Expand Up @@ -372,7 +379,7 @@ describe('createArticle slug generation (#285)', () => {
updatedAt: new Date(),
},
])
vi.mocked(db.insert).mockReturnValue(chain as never)
vi.mocked(db.insert).mockReturnValueOnce(chain as never)
mockCategoryFindFirst.mockResolvedValue({
id: 'category_1',
slug: 'getting-started',
Expand Down Expand Up @@ -676,6 +683,10 @@ describe('updateArticle with position and description', () => {
})

describe('recordArticleFeedback', () => {
beforeEach(() => {
mockArticleFindFirst.mockResolvedValue({ categoryId: 'category_1' })
})

it('inserts new feedback when no existing feedback', async () => {
mockFeedbackFindFirst.mockResolvedValue(null)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ const mockCategoryFindFirst = vi.fn()
const mockCategoryFindMany = vi.fn()
const mockSelectFrom = vi.fn()

function mockExistingCategory(id: string, parentId: string | null = null) {
mockCategoryFindFirst.mockResolvedValueOnce({
id,
slug: 'existing',
name: 'Existing',
description: null,
isPublic: true,
adminOnly: false,
position: 0,
parentId,
icon: null,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
deletedAt: null,
})
}

vi.mock('@/lib/server/db', () => ({
db: {
query: {
Expand Down Expand Up @@ -126,6 +143,9 @@ let restoreCategory: typeof import('../help-center.category.service').restoreCat

beforeEach(async () => {
vi.clearAllMocks()
mockCategoryFindFirst.mockReset().mockResolvedValue(null)
mockCategoryFindMany.mockReset().mockResolvedValue([])
mockSelectFrom.mockReset()
insertValuesCalls.length = 0
updateSetCalls.length = 0
updateWhereCalls.length = 0
Expand Down Expand Up @@ -434,11 +454,13 @@ describe('createCategory slug generation (#285)', () => {

describe('updateCategory slug generation (#285)', () => {
it('falls back to a generic slug when an explicit empty slug is given', async () => {
mockExistingCategory('category_1')
await updateCategory('category_1' as HelpCenterCategoryId, { slug: '' })
expect((updateSetCalls[0][0] as Record<string, unknown>).slug).toBe('category')
})

it('disambiguates an explicit slug that collides with another category', async () => {
mockExistingCategory('category_1')
mockCategoryFindFirst
.mockResolvedValueOnce({ id: 'category_other' })
.mockResolvedValueOnce(null)
Expand All @@ -447,6 +469,7 @@ describe('updateCategory slug generation (#285)', () => {
})

it('keeps an explicit slug that only collides with the same category', async () => {
mockExistingCategory('category_1')
mockCategoryFindFirst.mockResolvedValueOnce({ id: 'category_1' })
await updateCategory('category_1' as HelpCenterCategoryId, { slug: 'faq' })
expect((updateSetCalls[0][0] as Record<string, unknown>).slug).toBe('faq')
Expand Down Expand Up @@ -533,6 +556,7 @@ describe('createCategory with parentId and icon', () => {

describe('updateCategory with parentId and icon', () => {
it('passes parentId and icon in the update set', async () => {
mockExistingCategory('category_1')
mockCategoryFindMany.mockResolvedValue([
{ id: 'category_parent1', parentId: null },
{ id: 'category_1', parentId: null },
Expand All @@ -548,6 +572,7 @@ describe('updateCategory with parentId and icon', () => {
})

it('allows clearing parentId and icon by passing null', async () => {
mockExistingCategory('category_1')
mockCategoryFindMany.mockResolvedValue([{ id: 'category_1', parentId: null }])
await updateCategory('category_1' as HelpCenterCategoryId, {
parentId: null,
Expand Down Expand Up @@ -624,6 +649,7 @@ describe('createCategory hierarchy validation', () => {

describe('updateCategory hierarchy validation', () => {
it('rejects moving a category under itself', async () => {
mockExistingCategory('a')
mockCategoryFindMany.mockResolvedValue([
{ id: 'a', parentId: null },
{ id: 'b', parentId: 'a' },
Expand All @@ -634,6 +660,7 @@ describe('updateCategory hierarchy validation', () => {
})

it('rejects moving a category under its own descendant', async () => {
mockExistingCategory('a')
mockCategoryFindMany.mockResolvedValue([
{ id: 'a', parentId: null },
{ id: 'b', parentId: 'a' },
Expand All @@ -645,6 +672,7 @@ describe('updateCategory hierarchy validation', () => {
})

it('rejects moving a subtree such that the deepest leaf would exceed MAX_CATEGORY_DEPTH', async () => {
mockExistingCategory('b', 'a')
mockCategoryFindMany.mockResolvedValue([
{ id: 'a', parentId: null },
{ id: 'b', parentId: 'a' },
Expand All @@ -658,6 +686,7 @@ describe('updateCategory hierarchy validation', () => {
})

it('allows setting parentId to null (promoting to top-level)', async () => {
mockExistingCategory('b', 'a')
mockCategoryFindMany.mockResolvedValue([
{ id: 'a', parentId: null },
{ id: 'b', parentId: 'a' },
Expand Down Expand Up @@ -823,7 +852,7 @@ describe('listCategories with showDeleted option', () => {
const result = await listCategories({ showDeleted: true })
expect(result).toHaveLength(1)
expect(result[0].name).toBe('Deleted Category')
expect(mockCategoryFindMany).toHaveBeenCalledTimes(1)
expect(mockCategoryFindMany).toHaveBeenCalledTimes(2)
})

it('returns 0 article count for deleted categories with no deleted articles', async () => {
Expand Down
Loading