diff --git a/packages/urlstate/encoder/encoder.test.ts b/packages/urlstate/encoder/encoder.test.ts index 1fd3a2d6..f83774c5 100644 --- a/packages/urlstate/encoder/encoder.test.ts +++ b/packages/urlstate/encoder/encoder.test.ts @@ -19,6 +19,11 @@ describe('encoder', () => { const str = '"test #?\t=\n[]{}()%.'; expect(decode(encode(str))).toStrictEqual(str); }); + + it('single quote', () => { + const str = "'"; + expect(decode(encode(str))).toStrictEqual(str); + }); }); describe('number', () => { diff --git a/packages/urlstate/encoder/encoder.ts b/packages/urlstate/encoder/encoder.ts index ab66e632..856d2330 100644 --- a/packages/urlstate/encoder/encoder.ts +++ b/packages/urlstate/encoder/encoder.ts @@ -23,7 +23,9 @@ export function encode(payload: unknown): string { case "undefined": return SYMBOLS.undefined; default: - return JSON.stringify(payload).replaceAll('"', "'"); + return JSON.stringify(payload) + .replaceAll("'", "%27") + .replaceAll('"', "'"); } } @@ -46,7 +48,10 @@ export type Primitive = Exclude< * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#decode} */ export function decode(payload: string, fallback?: T) { - return parseJSON(payload.replaceAll("'", '"'), fallback as JSONCompatible); + return parseJSON( + payload.replaceAll("'", '"').replaceAll("%27", "'"), + fallback as JSONCompatible, + ); } export const decodePrimitive = (str: string) => { diff --git a/tests/useUrlState/main.spec.ts b/tests/useUrlState/main.spec.ts index 7861405f..0a101aa7 100644 --- a/tests/useUrlState/main.spec.ts +++ b/tests/useUrlState/main.spec.ts @@ -16,9 +16,9 @@ const delay = 1 test.describe('main tests', () => { const expectedUrl = - '?name=%27My+Name%27&age=33&agree_to_terms=true&tags=%5B%7B%27id%27%3A%271%27%2C%27value%27%3A%7B%27text%27%3A%27React.js%27%2C%27time%27%3A%272024-07-17T04%3A53%3A17.000Z%27%7D%7D%5D'; + '?name=%27My+Name%2527%27&age=33&agree_to_terms=true&tags=%5B%7B%27id%27%3A%271%27%2C%27value%27%3A%7B%27text%27%3A%27React.js%27%2C%27time%27%3A%272024-07-17T04%3A53%3A17.000Z%27%7D%7D%5D'; const expectedText = `{ - "name": "My Name", + "name": "My Name'", "age": 33, "agree_to_terms": true, "tags": [ @@ -31,7 +31,7 @@ test.describe('main tests', () => { } ] }`; - const values = { name: 'My Name', age: '33' }; + const values = { name: "My Name'", age: '33' }; for (const url of urls) { test(`fast concurent URL updates ${url}`, async ({ page }) => {