Fix firefox ability to load file, add firefox tests back (#1380)

## Launch Checklist

This PR fixes the problem with firefox not able to load a file when
clicking the open file button.
It does so by removing a very old dependency: `react-file-reader-input`
It uses the "regular" `<label><a/><input type="file" /><label>`
approach.
It adds tests for both firefox and chrome although I'm not sure the
firefox tests will stay...

 - [x] Briefly describe the changes in this PR.
 - [x] Write tests for all new functionality.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.
This commit is contained in:
Harel M
2025-09-15 08:15:00 +03:00
committed by GitHub
parent 5312d61598
commit b42afd0027
7 changed files with 36 additions and 34 deletions
+1
View File
@@ -16,6 +16,7 @@
- Fix modal close button possition - Fix modal close button possition
- Fixed an issue with the generation of tranlations - Fixed an issue with the generation of tranlations
- Fix missing spec info when clicking next to a property - Fix missing spec info when clicking next to a property
- Fix Firefox open file that stopped working due to react upgrade
- _...Add new stuff here..._ - _...Add new stuff here..._
## 3.0.0 ## 3.0.0
+16
View File
@@ -26,6 +26,22 @@ export default class MaputnikCypressHelper {
this.helper.when.wait(200); this.helper.when.wait(200);
this.helper.get.elementByTestId(element).realMouseUp(); this.helper.get.elementByTestId(element).realMouseUp();
}, },
openFileByFixture: (fixture: string, buttonTestId: string, inputTestId: string) => {
cy.window().then((win) => {
const file = {
text: cy.stub().resolves(cy.fixture(fixture).then(JSON.stringify)),
};
const fileHandle = {
getFile: cy.stub().resolves(file),
};
if (!win.showOpenFilePicker) {
this.helper.get.elementByTestId(inputTestId).selectFile("cypress/fixtures/" + fixture, { force: true });
} else {
cy.stub(win, "showOpenFilePicker").resolves([fileHandle]);
this.helper.get.elementByTestId(buttonTestId).click();
}
});
},
...this.helper.when, ...this.helper.when,
}; };
+3 -3
View File
@@ -109,9 +109,9 @@ export class MaputnikDriver {
this.helper.when.waitForResponse("example-style.json"); this.helper.when.waitForResponse("example-style.json");
}, },
chooseExampleFile: () => { chooseExampleFile: () => {
this.helper.get this.helper.given.fixture("example-style.json", "example-style.json");
.bySelector("type", "file") this.helper.when.openFileByFixture("example-style.json", "modal:open.file.button", "modal:open.file.input");
.selectFile("cypress/fixtures/example-style.json", { force: true }); this.helper.when.wait(200);
}, },
setStyle: ( setStyle: (
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "", styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "",
+7 -3
View File
@@ -18,10 +18,9 @@ describe("modals", () => {
then(get.elementByTestId("modal:open")).shouldNotExist(); then(get.elementByTestId("modal:open")).shouldNotExist();
}); });
it.skip("upload", () => { it("upload", () => {
// HM: I was not able to make the following choose file actually to select a file and close the modal...
when.chooseExampleFile(); when.chooseExampleFile();
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle(); then(get.fixture("example-style.json")).shouldEqualToStoredStyle();
}); });
describe("when click open url", () => { describe("when click open url", () => {
@@ -334,6 +333,7 @@ describe("modals", () => {
it("add variable", () => { it("add variable", () => {
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "value" } }, state: { key1: { default: "value" } },
}); });
@@ -344,6 +344,7 @@ describe("modals", () => {
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } }, state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
}); });
@@ -354,6 +355,7 @@ describe("modals", () => {
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.click("global-state-remove-variable", 0); when.click("global-state-remove-variable", 0);
when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key2: { default: "value" }, key3: { default: "value" } }, state: { key2: { default: "value" }, key3: { default: "value" } },
}); });
@@ -363,6 +365,7 @@ describe("modals", () => {
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.setValue("global-state-variable-key:0", "mykey"); when.setValue("global-state-variable-key:0", "mykey");
when.typeKeys("{enter}"); when.typeKeys("{enter}");
when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { mykey: { default: "value" } }, state: { mykey: { default: "value" } },
}); });
@@ -372,6 +375,7 @@ describe("modals", () => {
when.click("global-state-add-variable"); when.click("global-state-add-variable");
when.setValue("global-state-variable-value:0", "myvalue"); when.setValue("global-state-variable-value:0", "myvalue");
when.typeKeys("{enter}"); when.typeKeys("{enter}");
when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "myvalue" } }, state: { key1: { default: "myvalue" } },
}); });
-20
View File
@@ -51,7 +51,6 @@
"react-collapse": "^5.1.1", "react-collapse": "^5.1.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-file-reader-input": "^2.0.0",
"react-i18next": "^15.7.3", "react-i18next": "^15.7.3",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -86,7 +85,6 @@
"@types/react-collapse": "^5.0.4", "@types/react-collapse": "^5.0.4",
"@types/react-color": "^3.0.13", "@types/react-color": "^3.0.13",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^19.1.9",
"@types/react-file-reader-input": "^2.0.4",
"@types/string-hash": "^1.1.3", "@types/string-hash": "^1.1.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/wicg-file-system-access": "^2023.10.6", "@types/wicg-file-system-access": "^2023.10.6",
@@ -3225,15 +3223,6 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/react-file-reader-input": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/react-file-reader-input/-/react-file-reader-input-2.0.4.tgz",
"integrity": "sha512-Jqrfn+w42j8t8Q3npMXXKPdk+reIM0UHLKVc3ykrA7q7bN3Z62SGhsClZX0+Edlqm66lcKwmDQl+WMm+Xor7Xg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/reactcss": { "node_modules/@types/reactcss": {
"version": "1.2.12", "version": "1.2.12",
"resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.12.tgz", "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.12.tgz",
@@ -11681,15 +11670,6 @@
"react": "^19.1.1" "react": "^19.1.1"
} }
}, },
"node_modules/react-file-reader-input": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-file-reader-input/-/react-file-reader-input-2.0.0.tgz",
"integrity": "sha512-1XgkCpwMnNQsuOIy938UCntz8Xzwt9ECwHaH3cCfIQK1SPpH+y7gCYtqEcb6Rm0hAUq7Lp9+Ljoti9zGMswYrQ==",
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
}
},
"node_modules/react-hot-loader": { "node_modules/react-hot-loader": {
"version": "4.13.1", "version": "4.13.1",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz", "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz",
-2
View File
@@ -66,7 +66,6 @@
"react-collapse": "^5.1.1", "react-collapse": "^5.1.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-file-reader-input": "^2.0.0",
"react-i18next": "^15.7.3", "react-i18next": "^15.7.3",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -118,7 +117,6 @@
"@types/react-collapse": "^5.0.4", "@types/react-collapse": "^5.0.4",
"@types/react-color": "^3.0.13", "@types/react-color": "^3.0.13",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^19.1.9",
"@types/react-file-reader-input": "^2.0.4",
"@types/string-hash": "^1.1.3", "@types/string-hash": "^1.1.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/wicg-file-system-access": "^2023.10.6", "@types/wicg-file-system-access": "^2023.10.6",
+9 -6
View File
@@ -1,7 +1,6 @@
import React, { type FormEvent } from "react"; import React, { type FormEvent } from "react";
import {MdFileUpload} from "react-icons/md"; import {MdFileUpload} from "react-icons/md";
import {MdAddCircleOutline} from "react-icons/md"; import {MdAddCircleOutline} from "react-icons/md";
import FileReaderInput, { type Result } from "react-file-reader-input";
import { Trans, type WithTranslation, withTranslation } from "react-i18next"; import { Trans, type WithTranslation, withTranslation } from "react-i18next";
import ModalLoading from "./ModalLoading"; import ModalLoading from "./ModalLoading";
@@ -171,8 +170,10 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
// it is not guaranteed that the File System Access API is available on all // it is not guaranteed that the File System Access API is available on all
// browsers. If the function is not available, a fallback behavior is used. // browsers. If the function is not available, a fallback behavior is used.
onFileChanged = async (_: any, files: Result[]) => { onFileChanged = (files: FileList | null) => {
const [, file] = files[0]; if (!files) return;
if (files.length === 0) return;
const file = files[0];
const reader = new FileReader(); const reader = new FileReader();
this.clearError(); this.clearError();
@@ -246,13 +247,15 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
<div> <div>
{typeof window.showOpenFilePicker === "function" ? ( {typeof window.showOpenFilePicker === "function" ? (
<InputButton <InputButton
data-wd-key="modal:open.file.button"
className="maputnik-big-button" className="maputnik-big-button"
onClick={this.onOpenFile}><MdFileUpload/> {t("Open Style")} onClick={this.onOpenFile}><MdFileUpload/> {t("Open Style")}
</InputButton> </InputButton>
) : ( ) : (
<FileReaderInput onChange={this.onFileChanged} tabIndex={-1} aria-label={t("Open Style")}> <label>
<InputButton className="maputnik-upload-button"><MdFileUpload /> {t("Open Style")}</InputButton> <a className="maputnik-button maputnik-upload-button" aria-label={t("Open Style")}><MdFileUpload /> {t("Open Style")}</a>
</FileReaderInput> <input data-wd-key="modal:open.file.input" type="file" style={{ display: "none" }} onChange={(e) => this.onFileChanged(e.target.files)} />
</label>
)} )}
</div> </div>
</section> </section>