From 85be60382bfdd1db9eb1ffb6ccefb326ee8754d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Mon, 30 Sep 2024 14:03:47 +0200 Subject: [PATCH] fix(#1989): workaround for downloading metadata on iOS Safari --- CHANGELOG.md | 1 + govtool/frontend/src/utils/jsonUtils.ts | 35 +++++++++++++++---- .../src/utils/tests/jsonUtils.test.ts | 32 +++++++++++------ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1384c5b4..df11b06eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ changes. ### Fixed - Add missing testIds for submitted votes [Issue 1875](https://github.com/IntersectMBO/govtool/issues/1875) +- Provide workaround for iOS for downloading metadata on iOS [Issue 1989](https://github.com/IntersectMBO/govtool/issues/1989) ### Changed diff --git a/govtool/frontend/src/utils/jsonUtils.ts b/govtool/frontend/src/utils/jsonUtils.ts index 6b22a9c7c..04f499ed9 100644 --- a/govtool/frontend/src/utils/jsonUtils.ts +++ b/govtool/frontend/src/utils/jsonUtils.ts @@ -7,14 +7,26 @@ import { NodeObject } from "jsonld"; * If not provided, the default name will be "data.jsonld". */ export const downloadJson = (json: NodeObject, fileName?: string) => { - const jsonString = `data:text/jsonld;charset=utf-8,${encodeURIComponent( - JSON.stringify(json, null, 2), - )}`; + const blob = new Blob([JSON.stringify(json, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); const link = document.createElement("a"); - link.href = jsonString; + link.href = url; link.download = `${fileName || "data"}.jsonld`; - link.click(); + // Fallback: If iOS/Safari doesn't support `download`, open the data in a new tab + if ( + navigator.userAgent.includes("Safari") && + !navigator.userAgent.includes("Chrome") + ) { + window.open(url, "_blank"); + } else { + link.click(); + } + + document.body.removeChild(link); + URL.revokeObjectURL(url); }; /** @@ -30,5 +42,16 @@ export const downloadTextFile = (text: string, fileName?: string) => { link.href = url; link.download = `${fileName || "data"}.txt`; - link.click(); + // Fallback: If iOS/Safari doesn't support `download`, open the data in a new tab + if ( + navigator.userAgent.includes("Safari") && + !navigator.userAgent.includes("Chrome") + ) { + window.open(url, "_blank"); + } else { + link.click(); + } + + document.body.removeChild(link); + URL.revokeObjectURL(url); }; diff --git a/govtool/frontend/src/utils/tests/jsonUtils.test.ts b/govtool/frontend/src/utils/tests/jsonUtils.test.ts index f609c4f57..792bd37e5 100644 --- a/govtool/frontend/src/utils/tests/jsonUtils.test.ts +++ b/govtool/frontend/src/utils/tests/jsonUtils.test.ts @@ -2,6 +2,24 @@ import { vi } from "vitest"; import { downloadJson } from ".."; describe("downloadJson", () => { + beforeEach(() => { + global.URL.createObjectURL = vi.fn(() => "mocked-url"); + global.URL.revokeObjectURL = vi.fn(); + + // We should pass Node as an argument based on typing. + // But we are not testing this against Nodes. + /* eslint-disable @typescript-eslint/ban-ts-comment */ + // @ts-expect-error + vi.spyOn(document.body, "appendChild").mockImplementation(() => undefined); + // @ts-expect-error + vi.spyOn(document.body, "removeChild").mockImplementation(() => undefined); + }); + + afterEach(() => { + // Restore the mocks after each test + vi.restoreAllMocks(); + }); + it("downloads JSON with default file name", () => { const json = { name: "John Doe", age: 30 }; const linkMock = document.createElement("a"); @@ -12,13 +30,11 @@ describe("downloadJson", () => { downloadJson(json); - expect(linkMock.href).toBe( - "data:text/jsonld;charset=utf-8,%7B%0A%20%20%22name%22%3A%20%22John%20Doe%22%2C%0A%20%20%22age%22%3A%2030%0A%7D", - ); + expect(linkMock.href).toBe("http://localhost:3000/mocked-url"); + expect(linkMock.download).toBe("data.jsonld"); + expect(global.URL.createObjectURL).toHaveBeenCalled(); expect(clickMock).toHaveBeenCalled(); - - vi.restoreAllMocks(); }); it("downloads JSON with custom file name", () => { @@ -31,12 +47,8 @@ describe("downloadJson", () => { downloadJson(json, "custom"); - expect(linkMock.href).toBe( - "data:text/jsonld;charset=utf-8,%7B%0A%20%20%22name%22%3A%20%22John%20Doe%22%2C%0A%20%20%22age%22%3A%2030%0A%7D", - ); + expect(linkMock.href).toBe("http://localhost:3000/mocked-url"); expect(linkMock.download).toBe("custom.jsonld"); expect(clickMock).toHaveBeenCalled(); - - vi.restoreAllMocks(); }); });