Skip to content
Merged
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
35 changes: 22 additions & 13 deletions src/TodoistApi.comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
COMMENT_WITH_OPTIONALS_AS_NULL_TASK,
DEFAULT_AUTH_TOKEN,
DEFAULT_COMMENT,
DEFAULT_RAW_COMMENT,
DEFAULT_REQUEST_ID,
RAW_COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL,
RAW_COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT,
RAW_COMMENT_WITH_OPTIONALS_AS_NULL_TASK,
} from './testUtils/testDefaults'
import { getSyncBaseUri, ENDPOINT_REST_COMMENTS } from './consts/endpoints'
import { setupRestClientMock } from './testUtils/mocks'
Expand All @@ -19,7 +23,7 @@ describe('TodoistApi comment endpoints', () => {
test('calls get request with expected params', async () => {
const getCommentsArgs = { projectId: '12', limit: 10, cursor: '0' }
const requestMock = setupRestClientMock({
results: [DEFAULT_COMMENT],
results: [DEFAULT_RAW_COMMENT],
nextCursor: '123',
})
const api = getTarget()
Expand All @@ -37,13 +41,19 @@ describe('TodoistApi comment endpoints', () => {
})

test('returns result from rest client', async () => {
const mockResponses = [
DEFAULT_RAW_COMMENT,
RAW_COMMENT_WITH_OPTIONALS_AS_NULL_TASK,
RAW_COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT,
RAW_COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL,
]
const expectedComments = [
DEFAULT_COMMENT,
COMMENT_WITH_OPTIONALS_AS_NULL_TASK,
COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT,
COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL,
]
setupRestClientMock({ results: expectedComments, nextCursor: '123' })
setupRestClientMock({ results: mockResponses, nextCursor: '123' })
const api = getTarget()

const { results: comments, nextCursor } = await api.getComments({ taskId: '12' })
Expand All @@ -56,7 +66,7 @@ describe('TodoistApi comment endpoints', () => {
describe('getComment', () => {
test('calls get on expected url', async () => {
const commentId = '1'
const requestMock = setupRestClientMock(DEFAULT_COMMENT)
const requestMock = setupRestClientMock(DEFAULT_RAW_COMMENT)
const api = getTarget()

await api.getComment(commentId)
Expand All @@ -71,13 +81,12 @@ describe('TodoistApi comment endpoints', () => {
})

test('returns result from rest client', async () => {
const expectedComment = DEFAULT_COMMENT
setupRestClientMock(expectedComment)
setupRestClientMock(DEFAULT_RAW_COMMENT)
const api = getTarget()

const comment = await api.getComment('1')

expect(comment).toEqual(expectedComment)
expect(comment).toEqual(DEFAULT_COMMENT)
})
})

Expand All @@ -88,7 +97,7 @@ describe('TodoistApi comment endpoints', () => {
}

test('makes post request with expected params', async () => {
const requestMock = setupRestClientMock(DEFAULT_COMMENT)
const requestMock = setupRestClientMock(DEFAULT_RAW_COMMENT)
const api = getTarget()

await api.addComment(addCommentArgs, DEFAULT_REQUEST_ID)
Expand All @@ -105,13 +114,12 @@ describe('TodoistApi comment endpoints', () => {
})

test('returns result from rest client', async () => {
const expectedComment = DEFAULT_COMMENT
setupRestClientMock(expectedComment)
setupRestClientMock(DEFAULT_RAW_COMMENT)
const api = getTarget()

const comment = await api.addComment(addCommentArgs)

expect(comment).toEqual(expectedComment)
expect(comment).toEqual(DEFAULT_COMMENT)
})
})

Expand All @@ -122,7 +130,7 @@ describe('TodoistApi comment endpoints', () => {

test('makes post request with expected params', async () => {
const taskId = '1'
const requestMock = setupRestClientMock(DEFAULT_COMMENT, 204)
const requestMock = setupRestClientMock(DEFAULT_RAW_COMMENT, 204)
const api = getTarget()

await api.updateComment(taskId, updateCommentArgs, DEFAULT_REQUEST_ID)
Expand All @@ -139,13 +147,14 @@ describe('TodoistApi comment endpoints', () => {
})

test('returns success result from rest client', async () => {
const returnedComment = { ...DEFAULT_COMMENT, ...updateCommentArgs }
const returnedComment = { ...DEFAULT_RAW_COMMENT, content: updateCommentArgs.content }
const expectedComment = { ...DEFAULT_COMMENT, content: updateCommentArgs.content }
setupRestClientMock(returnedComment, 204)
const api = getTarget()

const result = await api.updateComment('1', updateCommentArgs)

expect(result).toEqual(returnedComment)
expect(result).toEqual(expectedComment)
})
})

Expand Down
77 changes: 56 additions & 21 deletions src/testUtils/testDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Section,
Task,
User,
Comment,
RawComment,
Attachment,
Duration,
Deadline,
Expand Down Expand Up @@ -34,6 +34,7 @@ const DEFAULT_USER_NAME = 'A User'
const DEFAULT_USER_EMAIL = 'atestuser@doist.com'
const DEFAULT_COMMENT_ID = '4'
const DEFAULT_COMMENT_CONTENT = 'A comment'
const DEFAULT_COMMENT_REACTIONS = { '👍': ['1234', '5678'] }

export const DEFAULT_AUTH_TOKEN = 'AToken'
export const DEFAULT_REQUEST_ID = 'ARequestID'
Expand Down Expand Up @@ -146,9 +147,16 @@ export const PROJECT_WITH_OPTIONALS_AS_NULL: Project = {

export const DEFAULT_SECTION: Section = {
id: DEFAULT_SECTION_ID,
name: DEFAULT_SECTION_NAME,
order: DEFAULT_ORDER,
userId: DEFAULT_USER_ID,
projectId: DEFAULT_PROJECT_ID,
addedAt: '2025-03-28T14:01:23.334881Z',
updatedAt: '2025-03-28T14:01:23.334885Z',
archivedAt: null,
name: DEFAULT_SECTION_NAME,
sectionOrder: DEFAULT_ORDER,
isArchived: false,
isDeleted: false,
isCollapsed: false,
}

export const INVALID_SECTION = {
Expand Down Expand Up @@ -192,30 +200,46 @@ export const INVALID_ATTACHMENT = {
uploadState: 'something random',
}

export const DEFAULT_COMMENT: Comment = {
export const DEFAULT_RAW_COMMENT: RawComment = {
id: DEFAULT_COMMENT_ID,
postedUid: DEFAULT_USER_ID,
content: DEFAULT_COMMENT_CONTENT,
taskId: null,
projectId: DEFAULT_PROJECT_ID,
attachment: DEFAULT_ATTACHMENT,
fileAttachment: DEFAULT_ATTACHMENT,
uidsToNotify: null,
isDeleted: false,
postedAt: DEFAULT_DATE,
reactions: DEFAULT_COMMENT_REACTIONS,
itemId: DEFAULT_TASK_ID,
}

export const DEFAULT_COMMENT = {
...DEFAULT_RAW_COMMENT,
taskId: DEFAULT_RAW_COMMENT.itemId,
itemId: undefined,
}

export const INVALID_COMMENT = {
...DEFAULT_COMMENT,
attachment: INVALID_ATTACHMENT,
...DEFAULT_RAW_COMMENT,
isDeleted: 'true',
}

export const COMMENT_WITH_OPTIONALS_AS_NULL_TASK: Comment = {
...DEFAULT_COMMENT,
projectId: null,
attachment: null,
export const RAW_COMMENT_WITH_OPTIONALS_AS_NULL_TASK: RawComment = {
...DEFAULT_RAW_COMMENT,
fileAttachment: null,
uidsToNotify: null,
reactions: null,
}

export const COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL: Comment = {
...DEFAULT_COMMENT,
attachment: {
...DEFAULT_ATTACHMENT,
export const COMMENT_WITH_OPTIONALS_AS_NULL_TASK = {
...RAW_COMMENT_WITH_OPTIONALS_AS_NULL_TASK,
taskId: RAW_COMMENT_WITH_OPTIONALS_AS_NULL_TASK.itemId,
itemId: undefined,
}

export const RAW_COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL: RawComment = {
...DEFAULT_RAW_COMMENT,
fileAttachment: {
resourceType: 'file',
fileName: null,
fileSize: null,
fileType: null,
Expand All @@ -229,8 +253,19 @@ export const COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL: Comment = {
},
}

export const COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT: Comment = {
...DEFAULT_COMMENT,
taskId: null,
attachment: null,
export const COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL = {
...RAW_COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL,
taskId: RAW_COMMENT_WITH_ATTACHMENT_WITH_OPTIONALS_AS_NULL.itemId,
itemId: undefined,
}

export const RAW_COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT: RawComment = {
...DEFAULT_RAW_COMMENT,
itemId: undefined,
projectId: DEFAULT_PROJECT_ID,
}

export const COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT = {
...RAW_COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT,
taskId: undefined,
}
58 changes: 49 additions & 9 deletions src/types/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,16 @@ export type ProjectViewStyle = 'list' | 'board' | 'calendar'

export const SectionSchema = z.object({
id: z.string(),
order: z.number().int(),
name: z.string(),
userId: z.string(),
projectId: z.string(),
addedAt: z.string(),
updatedAt: z.string(),
archivedAt: z.string().nullable(),
name: z.string(),
sectionOrder: z.number().int(),
isArchived: z.boolean(),
isDeleted: z.boolean(),
isCollapsed: z.boolean(),
})
/**
* Represents a section within a project, used to group tasks.
Expand Down Expand Up @@ -135,14 +142,47 @@ export const AttachmentSchema = z
*/
export interface Attachment extends z.infer<typeof AttachmentSchema> {}

export const CommentSchema = z.object({
id: z.string(),
taskId: z.string().nullable(),
projectId: z.string().nullable(),
content: z.string(),
postedAt: z.string(),
attachment: AttachmentSchema.nullable(),
export const RawCommentSchema = z
.object({
id: z.string(),
itemId: z.string().optional(),
projectId: z.string().optional(),
content: z.string(),
postedAt: z.string(),
fileAttachment: AttachmentSchema.nullable(),
postedUid: z.string(),
uidsToNotify: z.array(z.string()).nullable(),
reactions: z.record(z.string(), z.array(z.string())).nullable(),
isDeleted: z.boolean(),
})
.refine(
(data) => {
// At least one of itemId or projectId must be present
return (
(data.itemId !== undefined && data.projectId === undefined) ||
(data.itemId === undefined && data.projectId !== undefined)
)
},
{
message: 'Exactly one of itemId or projectId must be provided',
},
)

/**
* Represents the raw comment data as received from the API before transformation.
* @see https://developer.todoist.com/sync/v9/#notes
*/
export interface RawComment extends z.infer<typeof RawCommentSchema> {}

export const CommentSchema = RawCommentSchema.transform((data) => {
const { itemId, ...rest } = data
return {
...rest,
// Map itemId to taskId for backwards compatibility
taskId: itemId,
}
})

/**
* Represents a comment on a task or project in Todoist.
* @see https://developer.todoist.com/sync/v9/#notes
Expand Down
5 changes: 3 additions & 2 deletions src/utils/validators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
INVALID_COMMENT,
DEFAULT_USER,
INVALID_USER,
DEFAULT_RAW_COMMENT,
} from '../testUtils/testDefaults'
import {
validateTask,
Expand Down Expand Up @@ -155,7 +156,7 @@ describe('validators', () => {

describe('validateComment', () => {
test('validation passes for a valid comment', () => {
const result = validateComment(DEFAULT_COMMENT)
const result = validateComment(DEFAULT_RAW_COMMENT)
expect(result).toEqual(DEFAULT_COMMENT)
})

Expand All @@ -173,7 +174,7 @@ describe('validators', () => {
})

test('validation passes for valid comment array', () => {
const result = validateCommentArray([DEFAULT_COMMENT])
const result = validateCommentArray([DEFAULT_RAW_COMMENT])
expect(result).toEqual([DEFAULT_COMMENT])
})

Expand Down