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
127 changes: 127 additions & 0 deletions workspace-server/src/__tests__/services/DocsService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,133 @@ describe('DocsService', () => {
});
});

/** Helper to wrap paragraph elements in the standard mock doc structure. */
const mockDocWithElements = (...elements: Record<string, unknown>[]) => ({
data: {
tabs: [
{
documentTab: {
body: {
content: [{ paragraph: { elements } }],
},
},
},
],
},
});

it('should extract text from smart chips (date, person, rich link)', async () => {
const mockDoc = mockDocWithElements(
{ textRun: { content: 'Meeting on ' } },
{
dateElement: {
dateElementProperties: {
displayText: 'Jan 15, 2025',
timestamp: '1736899200',
},
},
},
{ textRun: { content: ' with ' } },
{
person: {
personProperties: {
name: 'John Doe',
email: 'john@example.com',
},
},
},
{ textRun: { content: ' - see ' } },
{
richLink: {
richLinkProperties: {
title: 'Project Plan',
uri: 'https://docs.google.com/document/d/abc123',
},
},
},
{ textRun: { content: '\n' } },
);
mockDocsAPI.documents.get.mockResolvedValue(mockDoc);

const result = await docsService.getText({ documentId: 'test-doc-id' });

expect(result.content[0].text).toBe(
'Meeting on Jan 15, 2025 with [John Doe](mailto:john@example.com) - see [Project Plan](https://docs.google.com/document/d/abc123)\n',
);
});

it.each([
{
name: 'person without name falls back to email',
element: {
person: {
personProperties: {
email: 'jane@example.com',
},
},
},
expected: '[jane@example.com](mailto:jane@example.com)',
},
{
name: 'person without email falls back to name only',
element: {
person: {
personProperties: {
name: 'John Doe',
},
},
},
expected: 'John Doe',
},
{
name: 'rich link without title falls back to uri',
element: {
richLink: {
richLinkProperties: {
uri: 'https://docs.google.com/spreadsheets/d/xyz',
},
},
},
expected:
'[https://docs.google.com/spreadsheets/d/xyz](https://docs.google.com/spreadsheets/d/xyz)',
},
{
name: 'rich link without uri falls back to title only',
element: {
richLink: {
richLinkProperties: {
title: 'Some Document',
},
},
},
expected: 'Some Document',
},
{
name: 'date without displayText falls back to timestamp',
element: {
dateElement: {
dateElementProperties: {
timestamp: '1736899200',
},
},
},
expected: '1736899200',
},
])(
'should fall back correctly when $name',
async ({ element, expected }) => {
mockDocsAPI.documents.get.mockResolvedValue(
mockDocWithElements(element),
);

const result = await docsService.getText({
documentId: 'test-doc-id',
});

expect(result.content[0].text).toBe(expected);
},
);

it('should include text from nested child tabs', async () => {
const mockDoc = {
data: {
Expand Down
33 changes: 33 additions & 0 deletions workspace-server/src/services/DocsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,16 @@ export class DocsService {
element.paragraph.elements?.forEach((pElement) => {
if (pElement.textRun && pElement.textRun.content) {
text += pElement.textRun.content;
} else if (pElement.person?.personProperties) {
Comment thread
allenhutchison marked this conversation as resolved.
text += this._renderPersonChip(pElement.person.personProperties);
} else if (pElement.richLink?.richLinkProperties) {
text += this._renderRichLinkChip(
pElement.richLink.richLinkProperties,
);
} else if (pElement.dateElement?.dateElementProperties) {
text += this._renderDateChip(
pElement.dateElement.dateElementProperties,
);
}
});
} else if (element.table) {
Expand All @@ -830,6 +840,29 @@ export class DocsService {
return text;
}

private _renderPersonChip(props: docs_v1.Schema$PersonProperties): string {
const { name, email } = props;
if (email) {
return `[${name || email}](mailto:${email})`;
}
return name || '';
}

private _renderRichLinkChip(
props: docs_v1.Schema$RichLinkProperties,
): string {
const { title, uri } = props;
if (uri) {
return `[${title || uri}](${uri})`;
}
return title || '';
}

private _renderDateChip(props: docs_v1.Schema$DateElementProperties): string {
const { displayText, timestamp } = props;
return displayText || timestamp || '';
}

public replaceText = async ({
documentId,
findText,
Expand Down
Loading