diff --git a/src/data-connect/data-connect-api-client-internal.ts b/src/data-connect/data-connect-api-client-internal.ts index 28284db6a4..6292d023aa 100644 --- a/src/data-connect/data-connect-api-client-internal.ts +++ b/src/data-connect/data-connect-api-client-internal.ts @@ -413,10 +413,7 @@ export class DataConnectApiClient { */ private objectToString(data: unknown): string { if (typeof data === 'string') { - const escapedString = data - .replace(/\\/g, '\\\\') // Replace \ with \\ - .replace(/"/g, '\\"'); // Replace " with \" - return `"${escapedString}"`; + return JSON.stringify(data); } if (typeof data === 'number' || typeof data === 'boolean' || data === null) { return String(data); diff --git a/test/unit/data-connect/data-connect-api-client-internal.spec.ts b/test/unit/data-connect/data-connect-api-client-internal.spec.ts index 628608a9e9..ba51086e77 100644 --- a/test/unit/data-connect/data-connect-api-client-internal.spec.ts +++ b/test/unit/data-connect/data-connect-api-client-internal.spec.ts @@ -928,4 +928,70 @@ describe('DataConnectApiClient CRUD helpers', () => { .to.be.rejectedWith(FirebaseDataConnectError, `${serverErrorString}. ${additionalErrorMessageForBulkImport}`); }); }); + + describe('Issue #3043: String serialization', () => { + it('should correctly escape special characters in strings during insert', async () => { + const data = { + content: 'Line 1\nLine 2', + }; + + await apiClient.insert(tableName, data); + const callArgs = executeGraphqlStub.firstCall.args[0]; + + // Expected part of the query: content: "Line 1\nLine 2" + // which means the string "Line 1\\nLine 2" should be present in the call args. + expect(callArgs).to.include('content: "Line 1\\nLine 2"'); + }); + + it('should correctly escape backslash', async () => { + const data = { + content: 'Backslash \\', + }; + + await apiClient.insert(tableName, data); + const callArgs = executeGraphqlStub.firstCall.args[0]; + + // "Backslash \\" + // Escaped for GraphQL: "Backslash \\\\" + expect(callArgs).to.include('content: "Backslash \\\\"'); + }); + + it('should correctly escape double quotes', async () => { + const data = { + content: 'Quote "test"', + }; + + await apiClient.insert(tableName, data); + const callArgs = executeGraphqlStub.firstCall.args[0]; + + // "Quote \"test\"" + // Escaped for GraphQL: "Quote \\"test\\"" + expect(callArgs).to.include('content: "Quote \\"test\\""'); + }); + + it('should correctly escape tab character', async () => { + const data = { + content: 'Tab\tCharacter', + }; + + await apiClient.insert(tableName, data); + const callArgs = executeGraphqlStub.firstCall.args[0]; + + // "Tab\tCharacter" + // Escaped for GraphQL: "Tab\\tCharacter" + expect(callArgs).to.include('content: "Tab\\tCharacter"'); + }); + + it('should correctly handle emojis', async () => { + const data = { + content: 'Emoji 😊', + }; + + await apiClient.insert(tableName, data); + const callArgs = executeGraphqlStub.firstCall.args[0]; + + // "Emoji 😊" + expect(callArgs).to.include('content: "Emoji 😊"'); + }); + }); });