Repository: chanind/hanzi-writer (ts)

Issue:
svg can't get the correct clip-path if url contains ')' or '('
et: <path clip-path="url(https://xxx.com?keyword=(1)#mask-145)

Pull request description:
escaping the URL passed to clip-path to handle parentheses
This PR wraps the url passed to `clip-path` in double quotes ("), and escapes any quotations inside the URL. This should fix the issue where URLs that have parentheses in them cause hanzi-writer to break.

fixes #215

Patch:
diff --git a/jest-jsdom-env.js b/jest-jsdom-env.js
new file mode 100644
index 00000000..6891f280
--- /dev/null
+++ b/jest-jsdom-env.js
@@ -0,0 +1,20 @@
+/**
+ * Hack to allow us to modify jsdom from within tests
+ * from https://github.com/facebook/jest/issues/5124#issuecomment-352749005
+ * */
+
+const JSDOMEnvironment = require('jest-environment-jsdom');
+
+module.exports = class JSDOMEnvironmentGlobal extends JSDOMEnvironment {
+  constructor(config) {
+    super(config);
+
+    this.global.jsdom = this.dom;
+  }
+
+  teardown() {
+    this.global.jsdom = null;
+
+    return super.teardown();
+  }
+};
diff --git a/package.json b/package.json
index 18cc9e93..8cd8d0ea 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
     "rootDir": "src",
     "coverageDirectory": "../coverage/",
     "testURL": "https://test.com/url#tag",
-    "collectCoverage": true
+    "collectCoverage": true,
+    "testEnvironment": "<rootDir>/../jest-jsdom-env"
   }
 }
diff --git a/src/renderers/svg/svgUtils.ts b/src/renderers/svg/svgUtils.ts
index 28070665..9850dc49 100644
--- a/src/renderers/svg/svgUtils.ts
+++ b/src/renderers/svg/svgUtils.ts
@@ -14,9 +14,9 @@ export function attrs(elm: Element, attrsMap: Record<string, string>) {
 export function urlIdRef(id: string) {
   let prefix = '';
   if (window.location && window.location.href) {
-    prefix = window.location.href.replace(/#[^#]*$/, '');
+    prefix = window.location.href.replace(/#[^#]*$/, '').replace(/"/gi, '%22');
   }
-  return `url(${prefix}#${id})`;
+  return `url("${prefix}#${id}")`;
 }
 
 export function removeElm(elm: Element | undefined) {


Test patch:
diff --git a/src/renderers/svg/__tests__/StrokeRenderer-test.ts b/src/renderers/svg/__tests__/StrokeRenderer-test.ts
index 6a490ff7..dbd47879 100644
--- a/src/renderers/svg/__tests__/StrokeRenderer-test.ts
+++ b/src/renderers/svg/__tests__/StrokeRenderer-test.ts
@@ -37,7 +37,7 @@ describe('StrokeRenderer', () => {
       'rgba(12,101,20,0.3)',
     );
     expect((target.svg.childNodes[1] as Element).getAttribute('clip-path')).toBe(
-      `url(https://test.com/url#${maskId})`,
+      `url("https://test.com/url#${maskId}")`,
     );
 
     expect(maskPath).toMatchSnapshot();
diff --git a/src/renderers/svg/__tests__/svgUtils-test.ts b/src/renderers/svg/__tests__/svgUtils-test.ts
new file mode 100644
index 00000000..383de634
--- /dev/null
+++ b/src/renderers/svg/__tests__/svgUtils-test.ts
@@ -0,0 +1,24 @@
+import { urlIdRef } from '../svgUtils';
+
+describe('urlIdRef', () => {
+  it('prepends the ID with the current window location', () => {
+    (global as any).jsdom.reconfigure({
+      url: 'https://test.com',
+    });
+    expect(urlIdRef('123-blah')).toEqual('url("https://test.com/#123-blah")');
+  });
+
+  it('escapes " characters in the URL', () => {
+    (global as any).jsdom.reconfigure({
+      url: 'https://test-"(ok).com',
+    });
+    expect(urlIdRef('123-blah')).toEqual('url("https://test-%22(ok).com/#123-blah")');
+  });
+
+  it('strips everything after # in the URL', () => {
+    (global as any).jsdom.reconfigure({
+      url: 'https://test.com/path#existing-hash',
+    });
+    expect(urlIdRef('123-blah')).toEqual('url("https://test.com/path#123-blah")');
+  });
+});