From e68ab31c5647e7b6f4cbf7982810c0f5309d6a4d Mon Sep 17 00:00:00 2001 From: Jerry Ren Date: Tue, 8 Sep 2026 11:42:41 +0800 Subject: [PATCH 1/4] Add link-column operations to the HTML page SDK --- README.md | 28 +++++++ src/apis/html-page-api.js | 36 +++++++++ src/sdk.js | 34 +++++++++ tests/html-page-api.test.js | 142 ++++++++++++++++++++++++++++++++++++ tests/sdk.test.js | 110 ++++++++++++++++++++++++++++ 5 files changed, 350 insertions(+) diff --git a/README.md b/README.md index 9bd7744..86c6232 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,34 @@ await sdk.addRow({ tableName: "TableName", rowData: { Name: "John", Age: 30 }, }); + + +// Add or delete one linked record in a link-column cell +await sdk.addLink({ + tableName: "Tasks", + rowId: "task-row-1", + linkColumnName: "Related projects", + otherRowId: "project-row-1", +}); +await sdk.deleteLink({ + tableName: "Tasks", + rowId: "task-row-1", + linkColumnName: "Related projects", + otherRowId: "project-row-1", +}); + +// Add or delete linked records for multiple rows/link columns +const linksData = [ + { + row_id: "task-row-1", + links: { + "Related projects": ["project-row-1", "project-row-2"], + "Related customers": ["customer-row-1"], + }, + }, +]; +await sdk.batchAddLinks({ tableName: "Tasks", linksData }); +await sdk.batchDeleteLinks({ tableName: "Tasks", linksData }); ``` ## Testing diff --git a/src/apis/html-page-api.js b/src/apis/html-page-api.js index 2377791..84866d7 100644 --- a/src/apis/html-page-api.js +++ b/src/apis/html-page-api.js @@ -147,6 +147,42 @@ class HTMLPageAPI { return this._sendDelete(url, data); } + addLink(page_id, table_name, row_id, link_column_name, other_row_id, preview_table_config) { + const url = `${this.server}api/v2.1/universal-apps/${this.appUuid}/html-page-links/`; + const data = { page_id, table_name, row_id, link_column_name, other_row_id }; + if (preview_table_config && typeof preview_table_config === 'object') { + data.preview_table_config = preview_table_config; + } + return this.req.post(url, data); + } + + deleteLink(page_id, table_name, row_id, link_column_name, other_row_id, preview_table_config) { + const url = `${this.server}api/v2.1/universal-apps/${this.appUuid}/html-page-links/`; + const data = { page_id, table_name, row_id, link_column_name, other_row_id }; + if (preview_table_config && typeof preview_table_config === 'object') { + data.preview_table_config = preview_table_config; + } + return this._sendDelete(url, data); + } + + addLinks(page_id, table_name, links_data, preview_table_config) { + const url = `${this.server}api/v2.1/universal-apps/${this.appUuid}/html-page-links/batch/`; + const data = { page_id, table_name, links_data }; + if (preview_table_config && typeof preview_table_config === 'object') { + data.preview_table_config = preview_table_config; + } + return this.req.post(url, data); + } + + deleteLinks(page_id, table_name, links_data, preview_table_config) { + const url = `${this.server}api/v2.1/universal-apps/${this.appUuid}/html-page-links/batch/`; + const data = { page_id, table_name, links_data }; + if (preview_table_config && typeof preview_table_config === 'object') { + data.preview_table_config = preview_table_config; + } + return this._sendDelete(url, data); + } + getUploadLink(page_id, upload_type = 'file') { const url = `${this.server}api/v2.1/universal-apps/${this.appUuid}/html-page-upload-link/`; return this.req.get(url, { params: { page_id, upload_type } }); diff --git a/src/sdk.js b/src/sdk.js index 5cb788f..175d30e 100644 --- a/src/sdk.js +++ b/src/sdk.js @@ -129,6 +129,40 @@ export class HTMLPageSDK { return this.htmlPageAPI.deleteRows(this.options.pageId, tableName, rowsIds, previewTableConfig); } + addLink({ tableName, rowId, linkColumnName, otherRowId }) { + const previewTableConfig = this._getPreviewTableConfig({ tableName }); + return this.htmlPageAPI.addLink( + this.options.pageId, + tableName, + rowId, + linkColumnName, + otherRowId, + previewTableConfig, + ); + } + + deleteLink({ tableName, rowId, linkColumnName, otherRowId }) { + const previewTableConfig = this._getPreviewTableConfig({ tableName }); + return this.htmlPageAPI.deleteLink( + this.options.pageId, + tableName, + rowId, + linkColumnName, + otherRowId, + previewTableConfig, + ); + } + + batchAddLinks({ tableName, linksData }) { + const previewTableConfig = this._getPreviewTableConfig({ tableName }); + return this.htmlPageAPI.addLinks(this.options.pageId, tableName, linksData, previewTableConfig); + } + + batchDeleteLinks({ tableName, linksData }) { + const previewTableConfig = this._getPreviewTableConfig({ tableName }); + return this.htmlPageAPI.deleteLinks(this.options.pageId, tableName, linksData, previewTableConfig); + } + uploadFile({ file }) { return this.htmlPageAPI.upload(this.options.pageId, file); } diff --git a/tests/html-page-api.test.js b/tests/html-page-api.test.js index 91caf8f..99c2887 100644 --- a/tests/html-page-api.test.js +++ b/tests/html-page-api.test.js @@ -423,6 +423,148 @@ describe('HTMLPageAPI.deleteRows(s)', () => { }); }); +describe('HTMLPageAPI link operations', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('adds a link to a link-column cell', () => { + const { api, post } = createApi(); + const response = { data: { success: true } }; + post.mockReturnValue(response); + + const result = api.addLink( + 'page-1', + 'Tasks', + 'task-row-1', + 'Related projects', + 'project-row-1', + ); + + expect(result).toBe(response); + expect(post).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/', + { + page_id: 'page-1', + table_name: 'Tasks', + row_id: 'task-row-1', + link_column_name: 'Related projects', + other_row_id: 'project-row-1', + }, + ); + }); + + it('deletes a link from a link-column cell', () => { + const { api, del } = createApi(); + const response = { data: { success: true } }; + del.mockReturnValue(response); + + const result = api.deleteLink( + 'page-1', + 'Tasks', + 'task-row-1', + 'Related projects', + 'project-row-1', + ); + + expect(result).toBe(response); + expect(del).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/', + { + headers: { 'Content-Type': 'application/json' }, + data: { + page_id: 'page-1', + table_name: 'Tasks', + row_id: 'task-row-1', + link_column_name: 'Related projects', + other_row_id: 'project-row-1', + }, + }, + ); + }); + + it('adds and deletes links in batch', () => { + const { api, post, del } = createApi(); + const linksData = [ + { + row_id: 'task-row-1', + links: { + 'Related projects': ['project-row-1', 'project-row-2'], + }, + }, + ]; + + api.addLinks('page-1', 'Tasks', linksData); + api.deleteLinks('page-1', 'Tasks', linksData); + + expect(post).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/batch/', + { + page_id: 'page-1', + table_name: 'Tasks', + links_data: linksData, + }, + ); + expect(del).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/batch/', + { + headers: { 'Content-Type': 'application/json' }, + data: { + page_id: 'page-1', + table_name: 'Tasks', + links_data: linksData, + }, + }, + ); + }); + + it('includes preview_table_config in link requests for ai_agent preview', () => { + const { api, post, del } = createApi(); + const previewTableConfig = { table_id: 'table-1', permissions: {} }; + const linksData = [{ row_id: 'task-row-1', links: { related: ['project-row-1'] } }]; + + api.addLink('ai_agent', 'Tasks', 'task-row-1', 'related', 'project-row-1', previewTableConfig); + api.deleteLink('ai_agent', 'Tasks', 'task-row-1', 'related', 'project-row-1', previewTableConfig); + api.addLinks('ai_agent', 'Tasks', linksData, previewTableConfig); + api.deleteLinks('ai_agent', 'Tasks', linksData, previewTableConfig); + + expect(post).toHaveBeenNthCalledWith(1, expect.any(String), { + page_id: 'ai_agent', + table_name: 'Tasks', + row_id: 'task-row-1', + link_column_name: 'related', + other_row_id: 'project-row-1', + preview_table_config: previewTableConfig, + }); + expect(del).toHaveBeenNthCalledWith(1, expect.any(String), { + headers: { 'Content-Type': 'application/json' }, + data: { + page_id: 'ai_agent', + table_name: 'Tasks', + row_id: 'task-row-1', + link_column_name: 'related', + other_row_id: 'project-row-1', + preview_table_config: previewTableConfig, + }, + }); + expect(post).toHaveBeenNthCalledWith(2, expect.any(String), { + page_id: 'ai_agent', + table_name: 'Tasks', + links_data: linksData, + preview_table_config: previewTableConfig, + }); + expect(del).toHaveBeenNthCalledWith(2, expect.any(String), { + headers: { 'Content-Type': 'application/json' }, + data: { + page_id: 'ai_agent', + table_name: 'Tasks', + links_data: linksData, + preview_table_config: previewTableConfig, + }, + }); + }); +}); + describe('HTMLPageAPI.upload', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/tests/sdk.test.js b/tests/sdk.test.js index af45a1f..3961831 100644 --- a/tests/sdk.test.js +++ b/tests/sdk.test.js @@ -10,6 +10,10 @@ const mockUpdateRow = jest.fn(); const mockDeleteRows = jest.fn(); const mockAddRows = jest.fn(); const mockUpdateRows = jest.fn(); +const mockAddLink = jest.fn(); +const mockDeleteLink = jest.fn(); +const mockAddLinks = jest.fn(); +const mockDeleteLinks = jest.fn(); const mockUpload = jest.fn(); jest.mock('../src/iframe-adapter', () => ({ @@ -36,6 +40,10 @@ jest.mock('../src/apis/html-page-api', () => { deleteRows: mockDeleteRows, addRows: mockAddRows, updateRows: mockUpdateRows, + addLink: mockAddLink, + deleteLink: mockDeleteLink, + addLinks: mockAddLinks, + deleteLinks: mockDeleteLinks, upload: mockUpload, })); }); @@ -241,6 +249,108 @@ describe('rows', () => { }); }); +describe('links', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('delegates single and batch link operations', () => { + const sdk = new HTMLPageSDK({ pageId: 'page-1' }); + sdk.htmlPageAPI = { + addLink: mockAddLink, + deleteLink: mockDeleteLink, + addLinks: mockAddLinks, + deleteLinks: mockDeleteLinks, + }; + const linksData = [ + { + row_id: 'task-row-1', + links: { 'Related projects': ['project-row-1', 'project-row-2'] }, + }, + ]; + + sdk.addLink({ + tableName: 'Tasks', + rowId: 'task-row-1', + linkColumnName: 'Related projects', + otherRowId: 'project-row-1', + }); + sdk.deleteLink({ + tableName: 'Tasks', + rowId: 'task-row-1', + linkColumnName: 'Related projects', + otherRowId: 'project-row-1', + }); + sdk.batchAddLinks({ tableName: 'Tasks', linksData }); + sdk.batchDeleteLinks({ tableName: 'Tasks', linksData }); + + expect(mockAddLink).toHaveBeenCalledWith( + 'page-1', + 'Tasks', + 'task-row-1', + 'Related projects', + 'project-row-1', + undefined, + ); + expect(mockDeleteLink).toHaveBeenCalledWith( + 'page-1', + 'Tasks', + 'task-row-1', + 'Related projects', + 'project-row-1', + undefined, + ); + expect(mockAddLinks).toHaveBeenCalledWith('page-1', 'Tasks', linksData, undefined); + expect(mockDeleteLinks).toHaveBeenCalledWith('page-1', 'Tasks', linksData, undefined); + }); + + it('includes the matching table permissions for ai_agent preview', () => { + const previewTableConfig = { + table_id: 'table-1', + table_name: 'Tasks', + permissions: { + edit_rows_permission: { enabled: true, columns_keys: ['related'] }, + }, + }; + const expectedConfig = { + table_id: 'table-1', + permissions: previewTableConfig.permissions, + }; + const sdk = new HTMLPageSDK({ pageId: 'ai_agent', previewTableConfigs: [previewTableConfig] }); + sdk.htmlPageAPI = { + addLink: mockAddLink, + deleteLink: mockDeleteLink, + addLinks: mockAddLinks, + deleteLinks: mockDeleteLinks, + }; + const linksData = [{ row_id: 'task-row-1', links: { related: ['project-row-1'] } }]; + + sdk.addLink({ + tableName: 'Tasks', + rowId: 'task-row-1', + linkColumnName: 'related', + otherRowId: 'project-row-1', + }); + sdk.deleteLink({ + tableName: 'Tasks', + rowId: 'task-row-1', + linkColumnName: 'related', + otherRowId: 'project-row-1', + }); + sdk.batchAddLinks({ tableName: 'Tasks', linksData }); + sdk.batchDeleteLinks({ tableName: 'Tasks', linksData }); + + expect(mockAddLink).toHaveBeenCalledWith( + 'ai_agent', 'Tasks', 'task-row-1', 'related', 'project-row-1', expectedConfig, + ); + expect(mockDeleteLink).toHaveBeenCalledWith( + 'ai_agent', 'Tasks', 'task-row-1', 'related', 'project-row-1', expectedConfig, + ); + expect(mockAddLinks).toHaveBeenCalledWith('ai_agent', 'Tasks', linksData, expectedConfig); + expect(mockDeleteLinks).toHaveBeenCalledWith('ai_agent', 'Tasks', linksData, expectedConfig); + }); +}); + describe('upload', () => { it('delegates upload calls to HTMLPageAPI with the current pageId', () => { const sdk = new HTMLPageSDK({ pageId: 'page-1' }); From 62a19b6455216f8efca13c13a59655932dbf02cb Mon Sep 17 00:00:00 2001 From: Jerry Ren Date: Tue, 8 Sep 2026 11:47:43 +0800 Subject: [PATCH 2/4] Remove link-column API examples from README --- README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/README.md b/README.md index 86c6232..9bd7744 100644 --- a/README.md +++ b/README.md @@ -88,34 +88,6 @@ await sdk.addRow({ tableName: "TableName", rowData: { Name: "John", Age: 30 }, }); - - -// Add or delete one linked record in a link-column cell -await sdk.addLink({ - tableName: "Tasks", - rowId: "task-row-1", - linkColumnName: "Related projects", - otherRowId: "project-row-1", -}); -await sdk.deleteLink({ - tableName: "Tasks", - rowId: "task-row-1", - linkColumnName: "Related projects", - otherRowId: "project-row-1", -}); - -// Add or delete linked records for multiple rows/link columns -const linksData = [ - { - row_id: "task-row-1", - links: { - "Related projects": ["project-row-1", "project-row-2"], - "Related customers": ["customer-row-1"], - }, - }, -]; -await sdk.batchAddLinks({ tableName: "Tasks", linksData }); -await sdk.batchDeleteLinks({ tableName: "Tasks", linksData }); ``` ## Testing From 5cbbb01bfc33b86e29119de991978432904f61b3 Mon Sep 17 00:00:00 2001 From: Jerry Ren Date: Tue, 8 Sep 2026 16:15:17 +0800 Subject: [PATCH 3/4] Test link operations return updated rows --- tests/html-page-api.test.js | 43 +++++++++++++++++++++++++++++-------- tests/sdk.test.js | 41 +++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/tests/html-page-api.test.js b/tests/html-page-api.test.js index 99c2887..7e346d7 100644 --- a/tests/html-page-api.test.js +++ b/tests/html-page-api.test.js @@ -428,9 +428,15 @@ describe('HTMLPageAPI link operations', () => { jest.clearAllMocks(); }); - it('adds a link to a link-column cell', () => { + it('adds a link and returns the updated row', () => { const { api, post } = createApi(); - const response = { data: { success: true } }; + const updatedRow = { + _id: 'task-row-1', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + ], + }; + const response = { data: { success: true, row: updatedRow } }; post.mockReturnValue(response); const result = api.addLink( @@ -442,6 +448,7 @@ describe('HTMLPageAPI link operations', () => { ); expect(result).toBe(response); + expect(result.data.row).toEqual(updatedRow); expect(post).toHaveBeenCalledWith( 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/', { @@ -454,9 +461,10 @@ describe('HTMLPageAPI link operations', () => { ); }); - it('deletes a link from a link-column cell', () => { + it('deletes a link and returns the updated row', () => { const { api, del } = createApi(); - const response = { data: { success: true } }; + const updatedRow = { _id: 'task-row-1', 'Related projects': [] }; + const response = { data: { success: true, row: updatedRow } }; del.mockReturnValue(response); const result = api.deleteLink( @@ -468,6 +476,7 @@ describe('HTMLPageAPI link operations', () => { ); expect(result).toBe(response); + expect(result.data.row).toEqual(updatedRow); expect(del).toHaveBeenCalledWith( 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/', { @@ -483,7 +492,7 @@ describe('HTMLPageAPI link operations', () => { ); }); - it('adds and deletes links in batch', () => { + it('adds and deletes links in batch and returns the updated rows', () => { const { api, post, del } = createApi(); const linksData = [ { @@ -493,10 +502,26 @@ describe('HTMLPageAPI link operations', () => { }, }, ]; - - api.addLinks('page-1', 'Tasks', linksData); - api.deleteLinks('page-1', 'Tasks', linksData); - + const addedRows = [{ + _id: 'task-row-1', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + { row_id: 'project-row-2', display_value: 'Project 2' }, + ], + }]; + const deletedRows = [{ _id: 'task-row-1', 'Related projects': [] }]; + const addResponse = { data: { success: true, rows: addedRows } }; + const deleteResponse = { data: { success: true, rows: deletedRows } }; + post.mockReturnValue(addResponse); + del.mockReturnValue(deleteResponse); + + const addResult = api.addLinks('page-1', 'Tasks', linksData); + const deleteResult = api.deleteLinks('page-1', 'Tasks', linksData); + + expect(addResult).toBe(addResponse); + expect(addResult.data.rows).toEqual(addedRows); + expect(deleteResult).toBe(deleteResponse); + expect(deleteResult.data.rows).toEqual(deletedRows); expect(post).toHaveBeenCalledWith( 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-links/batch/', { diff --git a/tests/sdk.test.js b/tests/sdk.test.js index 3961831..ecb11f3 100644 --- a/tests/sdk.test.js +++ b/tests/sdk.test.js @@ -268,22 +268,51 @@ describe('links', () => { links: { 'Related projects': ['project-row-1', 'project-row-2'] }, }, ]; - - sdk.addLink({ + const addedRow = { + _id: 'task-row-1', + 'Related projects': [{ row_id: 'project-row-1', display_value: 'Project 1' }], + }; + const deletedRow = { _id: 'task-row-1', 'Related projects': [] }; + const addedRows = [{ + _id: 'task-row-1', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + { row_id: 'project-row-2', display_value: 'Project 2' }, + ], + }]; + const deletedRows = [{ _id: 'task-row-1', 'Related projects': [] }]; + const addResponse = { data: { success: true, row: addedRow } }; + const deleteResponse = { data: { success: true, row: deletedRow } }; + const batchAddResponse = { data: { success: true, rows: addedRows } }; + const batchDeleteResponse = { data: { success: true, rows: deletedRows } }; + mockAddLink.mockReturnValue(addResponse); + mockDeleteLink.mockReturnValue(deleteResponse); + mockAddLinks.mockReturnValue(batchAddResponse); + mockDeleteLinks.mockReturnValue(batchDeleteResponse); + + const addResult = sdk.addLink({ tableName: 'Tasks', rowId: 'task-row-1', linkColumnName: 'Related projects', otherRowId: 'project-row-1', }); - sdk.deleteLink({ + const deleteResult = sdk.deleteLink({ tableName: 'Tasks', rowId: 'task-row-1', linkColumnName: 'Related projects', otherRowId: 'project-row-1', }); - sdk.batchAddLinks({ tableName: 'Tasks', linksData }); - sdk.batchDeleteLinks({ tableName: 'Tasks', linksData }); - + const batchAddResult = sdk.batchAddLinks({ tableName: 'Tasks', linksData }); + const batchDeleteResult = sdk.batchDeleteLinks({ tableName: 'Tasks', linksData }); + + expect(addResult).toBe(addResponse); + expect(addResult.data.row).toEqual(addedRow); + expect(deleteResult).toBe(deleteResponse); + expect(deleteResult.data.row).toEqual(deletedRow); + expect(batchAddResult).toBe(batchAddResponse); + expect(batchAddResult.data.rows).toEqual(addedRows); + expect(batchDeleteResult).toBe(batchDeleteResponse); + expect(batchDeleteResult.data.rows).toEqual(deletedRows); expect(mockAddLink).toHaveBeenCalledWith( 'page-1', 'Tasks', From 6e401d2735c88eb2ffd18878d7808e2cde60c92b Mon Sep 17 00:00:00 2001 From: Jerry Ren Date: Tue, 8 Sep 2026 18:33:38 +0800 Subject: [PATCH 4/4] Update row tests for link-column replacements --- tests/html-page-api.test.js | 90 ++++++++++++++++++++++++++++++++++++- tests/sdk.test.js | 70 +++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/tests/html-page-api.test.js b/tests/html-page-api.test.js index 7e346d7..ce3b370 100644 --- a/tests/html-page-api.test.js +++ b/tests/html-page-api.test.js @@ -329,8 +329,10 @@ describe('HTMLPageAPI.updateRow(s)', () => { const { api, put } = createApi(); const rowsData = [{ row_id: 'row-1', Name: 'Jane' }]; const response = { - data: { success: true }, - rows: [{ row_id: 'row-1', '0000': 'Jane' }], + data: { + success: true, + rows: [{ _id: 'row-1', '0000': 'Jane' }], + }, }; put.mockReturnValue(response); @@ -350,6 +352,90 @@ describe('HTMLPageAPI.updateRow(s)', () => { }, ); }); + + it('updateRow sends link-column row ids and returns expanded link values', () => { + const { api, put } = createApi(); + const rowData = { + Name: 'Updated task', + 'Related projects': ['project-row-1', 'project-row-2'], + }; + const updatedRow = { + _id: 'task-row-1', + Name: 'Updated task', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + { row_id: 'project-row-2', display_value: 'Project 2' }, + ], + }; + const response = { data: { success: true, row: updatedRow } }; + put.mockReturnValue(response); + + const result = api.updateRow('page-1', 'Tasks', 'task-row-1', rowData); + + expect(result.data.row).toEqual(updatedRow); + expect(put).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-rows/', + { + page_id: 'page-1', + table_name: 'Tasks', + row_id: 'task-row-1', + row_data: rowData, + }, + { + headers: { 'Content-Type': 'application/json' }, + }, + ); + }); + + it('updateRows sends link-column row ids and returns expanded link values', () => { + const { api, put } = createApi(); + const rowsData = [ + { + row_id: 'task-row-1', + row: { + Name: 'Updated task 1', + 'Related projects': ['project-row-1', 'project-row-2'], + }, + }, + { + row_id: 'task-row-2', + row: { + 'Related projects': [], + }, + }, + ]; + const updatedRows = [ + { + _id: 'task-row-1', + Name: 'Updated task 1', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + { row_id: 'project-row-2', display_value: 'Project 2' }, + ], + }, + { + _id: 'task-row-2', + 'Related projects': [], + }, + ]; + const response = { data: { success: true, rows: updatedRows } }; + put.mockReturnValue(response); + + const result = api.updateRows('page-1', 'Tasks', rowsData); + + expect(result.data.rows).toEqual(updatedRows); + expect(put).toHaveBeenCalledWith( + 'https://example.com/api/v2.1/universal-apps/app-uuid/html-page-rows/batch/', + { + page_id: 'page-1', + table_name: 'Tasks', + rows_data: rowsData, + }, + { + headers: { 'Content-Type': 'application/json' }, + }, + ); + }); }); describe('HTMLPageAPI.deleteRows(s)', () => { diff --git a/tests/sdk.test.js b/tests/sdk.test.js index ecb11f3..ed84328 100644 --- a/tests/sdk.test.js +++ b/tests/sdk.test.js @@ -223,8 +223,10 @@ describe('rows', () => { inserted_row_count: 1, }; const updateRowsResponse = { - data: { success: true }, - rows: [{ row_id: 'row-1', '0000': 'Jane' }], + data: { + success: true, + rows: [{ _id: 'row-1', '0000': 'Jane' }], + }, }; mockAddRow.mockReturnValue(addRowResponse); @@ -237,16 +239,76 @@ describe('rows', () => { expect(sdk.updateRow({ tableName: 'TableName', rowId: 'row-1', rowData: { Name: 'Jane' } })).toEqual(updateRowResponse); expect(sdk.deleteRow({ tableName: 'TableName', rowId: 'row-1' })).toEqual(deleteRowsResponse); expect(sdk.batchAddRows({ tableName: 'TableName', rowsData: [{ Name: 'John' }] })).toEqual(addRowsResponse); - expect(sdk.batchUpdateRows({ tableName: 'TableName', rowsData: [{ row_id: 'row-1', Name: 'Jane' }] })).toEqual(updateRowsResponse); + expect(sdk.batchUpdateRows({ + tableName: 'TableName', + rowsData: [{ row_id: 'row-1', row: { Name: 'Jane' } }], + })).toEqual(updateRowsResponse); expect(sdk.batchDeleteRows({ tableName: 'TableName', rowsIds: ['row-1'] })).toEqual(deleteRowsResponse); expect(mockAddRow).toHaveBeenCalledWith('page-1', 'TableName', { Name: 'John' }, undefined); expect(mockUpdateRow).toHaveBeenCalledWith('page-1', 'TableName', 'row-1', { Name: 'Jane' }, undefined); expect(mockDeleteRows).toHaveBeenCalledWith('page-1', 'TableName', ['row-1'], undefined); expect(mockAddRows).toHaveBeenCalledWith('page-1', 'TableName', [{ Name: 'John' }], undefined); - expect(mockUpdateRows).toHaveBeenCalledWith('page-1', 'TableName', [{ row_id: 'row-1', Name: 'Jane' }], undefined); + expect(mockUpdateRows).toHaveBeenCalledWith( + 'page-1', + 'TableName', + [{ row_id: 'row-1', row: { Name: 'Jane' } }], + undefined, + ); expect(mockDeleteRows).toHaveBeenCalledTimes(2); }); + + it('updateRow and batchUpdateRows support link-column replacements', () => { + const sdk = new HTMLPageSDK({ pageId: 'page-1' }); + sdk.htmlPageAPI = { + updateRow: mockUpdateRow, + updateRows: mockUpdateRows, + }; + const rowData = { + Name: 'Updated task', + 'Related projects': ['project-row-1', 'project-row-2'], + }; + const rowsData = [ + { + row_id: 'task-row-1', + row: rowData, + }, + { + row_id: 'task-row-2', + row: { 'Related projects': [] }, + }, + ]; + const updatedRow = { + _id: 'task-row-1', + Name: 'Updated task', + 'Related projects': [ + { row_id: 'project-row-1', display_value: 'Project 1' }, + { row_id: 'project-row-2', display_value: 'Project 2' }, + ], + }; + const updatedRows = [ + updatedRow, + { _id: 'task-row-2', 'Related projects': [] }, + ]; + const updateResponse = { data: { success: true, row: updatedRow } }; + const batchUpdateResponse = { data: { success: true, rows: updatedRows } }; + mockUpdateRow.mockReturnValue(updateResponse); + mockUpdateRows.mockReturnValue(batchUpdateResponse); + + const result = sdk.updateRow({ tableName: 'Tasks', rowId: 'task-row-1', rowData }); + const batchResult = sdk.batchUpdateRows({ tableName: 'Tasks', rowsData }); + + expect(result.data.row).toEqual(updatedRow); + expect(batchResult.data.rows).toEqual(updatedRows); + expect(mockUpdateRow).toHaveBeenCalledWith( + 'page-1', + 'Tasks', + 'task-row-1', + rowData, + undefined, + ); + expect(mockUpdateRows).toHaveBeenCalledWith('page-1', 'Tasks', rowsData, undefined); + }); }); describe('links', () => {