Skip to content

Commit

Permalink
[unticketed] fix webkit / chrome error with csv download filename (#3756
Browse files Browse the repository at this point in the history
)
  • Loading branch information
doug-s-nava authored Feb 4, 2025
1 parent bc94d50 commit 9805736
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"use client";

import { getConfiguredDayJs } from "src/utils/dateUtil";
import { saveBlobToFile } from "src/utils/generalUtils";

import { ReadonlyURLSearchParams } from "next/navigation";

Expand All @@ -17,16 +20,9 @@ export const downloadSearchResultsCSV = async (
throw new Error(`Unsuccessful csv download. ${response.status}`);
}
const csvBlob = await response.blob();
location.assign(
URL.createObjectURL(
new File(
[csvBlob],
`grants-search-${getConfiguredDayJs()(new Date()).format("YYYYMMDDHHmm")}.csv`,
{
type: "data:text/csv",
},
),
),
saveBlobToFile(
csvBlob,
`grants-search-${getConfiguredDayJs()(new Date()).format("YYYYMMDDHHmm")}.csv`,
);
} catch (e) {
console.error(e);
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/utils/generalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,18 @@ export const encodeText = (valueToEncode: string) =>
export const stringToBoolean = (
mightRepresentABoolean: string | undefined,
): boolean => mightRepresentABoolean === "true";

// a hack to get filenames to work on blob based downloads across all browsers
// see https://stackoverflow.com/a/48968694
export const saveBlobToFile = (blob: Blob, filename: string) => {
const temporaryLink = document.createElement("a");
document.body.appendChild(temporaryLink);
const url = window.URL.createObjectURL(blob);
temporaryLink.href = url;
temporaryLink.download = filename;
temporaryLink.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
document.body.removeChild(temporaryLink);
}, 0);
};
2 changes: 1 addition & 1 deletion frontend/tests/e2e/search/search-download.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ test.describe("Search results export", () => {
.locator('div[data-testid="search-download-button-container"] > button')
.click();
const download = await downloadPromise;
expect(download.url()).toBeTruthy();
expect(download.suggestedFilename()).toMatch(/grants-search-\d+\.csv/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ import { ReadonlyURLSearchParams } from "next/navigation";

const fakeBlob = new Blob();

const getFakeFile = () =>
new File(
[fakeBlob],
`grants-search-${getConfiguredDayJs()(new Date()).format("YYYYMMDDHHmm")}.csv`,
{
type: "data:text/csv",
},
);

const mockBlob = jest.fn(() => fakeBlob);

const mockFetch = jest.fn(() =>
Expand All @@ -24,29 +15,22 @@ const mockFetch = jest.fn(() =>
ok: true,
}),
);
const mockCreateObjectUrl = jest.fn(() => "an object url");
const mockLocationAssign = jest.fn();
const mockSaveBlobToFile = jest.fn();

jest.mock("src/utils/generalUtils", () => ({
saveBlobToFile: (...args: unknown[]): unknown => mockSaveBlobToFile(...args),
}));

describe("downloadSearchResultsCSV", () => {
let originalFetch: typeof global.fetch;
let originalCreateObjectURL: typeof global.URL.createObjectURL;
let originalLocationAssign: typeof global.location.assign;

beforeEach(() => {
originalCreateObjectURL = global.URL.createObjectURL;
originalLocationAssign = global.location.assign;
Object.defineProperty(global, "location", {
value: { assign: mockLocationAssign },
});
originalFetch = global.fetch;
global.fetch = mockFetch as jest.Mock;
global.URL.createObjectURL = mockCreateObjectUrl;
});

afterEach(() => {
global.fetch = originalFetch;
global.location.assign = originalLocationAssign;
global.URL.createObjectURL = originalCreateObjectURL;
jest.clearAllMocks();
});

Expand All @@ -66,11 +50,13 @@ describe("downloadSearchResultsCSV", () => {
expect(mockBlob).toHaveBeenCalledTimes(1);
});

it("sets location with blob result", async () => {
it("calls save to blob with the blob result", async () => {
await downloadSearchResultsCSV(
new ReadonlyURLSearchParams("status=fake&agency=alsoFake"),
);
expect(mockCreateObjectUrl).toHaveBeenCalledWith(getFakeFile());
expect(mockLocationAssign).toHaveBeenCalledWith("an object url");
expect(mockSaveBlobToFile).toHaveBeenCalledWith(
fakeBlob,
`grants-search-${getConfiguredDayJs()(new Date()).format("YYYYMMDDHHmm")}.csv`,
);
});
});

0 comments on commit 9805736

Please sign in to comment.