@octopusdeploy/design-system-components
Version:
The design systems component library.
310 lines (309 loc) • 18 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const testing_library_1 = require("@octopusdeploy/testing-library");
const react_1 = require("@testing-library/react");
const user_event_1 = __importDefault(require("@testing-library/user-event"));
const react_2 = require("react");
const vitest_1 = require("vitest");
const FileUpload_1 = require("../FileUpload");
(0, vitest_1.describe)("FileUpload", () => {
(0, vitest_1.test)("renders with correct label", () => {
const fileUpload = renderFileUpload({ label: "Custom Label" });
fileUpload.shouldHaveLabel("Custom Label");
fileUpload.shouldBeInDocument();
});
(0, vitest_1.test)("renders with description", () => {
const description = "This is a helpful description";
const fileUpload = renderFileUpload({ description });
fileUpload.shouldHaveDescription(description);
});
(0, vitest_1.test)("renders the files provided via value", () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({ initialValue: [{ file }] });
fileUpload.shouldHaveFile("test.txt");
});
(0, vitest_1.describe)("selection", () => {
(0, vitest_1.test)("emits the selected file as a SelectedFile and displays it", async () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({ label: "Upload file" });
await fileUpload.selectFile(file);
fileUpload.shouldHaveEmittedFiles([file]);
fileUpload.shouldHaveFile("test.txt");
});
(0, vitest_1.test)("removes a file when the remove button is clicked", async () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({ label: "Upload file" });
await fileUpload.selectFile(file);
fileUpload.shouldHaveFile("test.txt");
await fileUpload.clickRemoveButton("test.txt");
fileUpload.shouldNotHaveFile("test.txt");
fileUpload.shouldHaveEmittedFiles([]);
});
(0, vitest_1.test)("appends files when filesLimit is greater than 1", async () => {
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const fileUpload = renderFileUpload({ label: "Upload files", filesLimit: 3 });
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
fileUpload.shouldHaveFile("file1.txt");
fileUpload.shouldHaveFile("file2.txt");
});
(0, vitest_1.test)("replaces the file when filesLimit is 1 and a new file is selected", async () => {
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const fileUpload = renderFileUpload({ label: "Upload file", filesLimit: 1 });
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
fileUpload.shouldNotHaveFile("file1.txt");
fileUpload.shouldHaveFile("file2.txt");
fileUpload.shouldHaveEmittedFiles([file2]);
});
});
(0, vitest_1.test)("accepts a file that matches the accepted file types", async () => {
const validFile = createMockFile("document.txt");
const fileUpload = renderFileUpload({ label: "Upload file", acceptedFileTypes: [".txt", ".pdf"] });
await fileUpload.selectFile(validFile);
fileUpload.shouldHaveFile("document.txt");
fileUpload.shouldHaveEmittedFiles([validFile]);
});
(0, vitest_1.describe)("rejected files are added per-file", () => {
(0, vitest_1.test)("adds a file that exceeds the size limit to the list with a per-file error", async () => {
const oneGbBytes = 1024 * 1024 * 1024;
const largeFile = createMockFile("test.txt", oneGbBytes + 1);
const fileUpload = renderFileUpload({ label: "Upload file", maxFileSizeBytes: oneGbBytes });
await fileUpload.selectFile(largeFile);
fileUpload.shouldHaveFile("test.txt");
fileUpload.shouldHaveFileLevelError("test.txt", "File test.txt is too big. Size limit is 1 GB.");
fileUpload.shouldHaveEmittedFiles([largeFile]);
});
(0, vitest_1.test)("adds a file with an unsupported type to the list with a per-file error", async () => {
const invalidFile = createMockFile("image.jpg");
const fileUpload = renderFileUpload({ label: "Upload file", acceptedFileTypes: [".txt"] });
await fileUpload.selectFile(invalidFile);
fileUpload.shouldHaveFile("image.jpg");
fileUpload.shouldHaveFileLevelError("image.jpg", "File type for image.jpg is not supported.");
});
(0, vitest_1.test)("accepts the valid files and adds the rejected one with a per-file error", async () => {
const validFile1 = createMockFile("valid1.txt", 500);
const validFile2 = createMockFile("valid2.txt", 700);
const tooLargeFile = createMockFile("toolarge.txt", 2000);
const fileUpload = renderFileUpload({ label: "Upload files", filesLimit: 5, maxFileSizeBytes: 1024 });
await fileUpload.selectFile([validFile1, validFile2, tooLargeFile]);
fileUpload.shouldHaveFile("valid1.txt");
fileUpload.shouldHaveFile("valid2.txt");
fileUpload.shouldHaveFileLevelError("toolarge.txt", "File toolarge.txt is too big. Size limit is 1 KB.");
fileUpload.shouldHaveEmittedFiles([validFile1, validFile2, tooLargeFile]);
});
});
(0, vitest_1.describe)("files limit", () => {
(0, vitest_1.test)("accepts files up to the limit and drops the overflow with a global message", async () => {
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const file3 = createMockFile("file3.txt");
const fileUpload = renderFileUpload({ label: "Upload files", filesLimit: 2 });
await fileUpload.selectFile([file1, file2, file3]);
fileUpload.shouldHaveFile("file1.txt");
fileUpload.shouldHaveFile("file2.txt");
fileUpload.shouldNotHaveFile("file3.txt");
fileUpload.shouldHaveError("You can upload up to 2 files");
fileUpload.shouldHaveEmittedFiles([file1, file2]);
});
(0, vitest_1.test)("reports the overflow when a later selection exceeds the limit", async () => {
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const file3 = createMockFile("file3.txt");
const fileUpload = renderFileUpload({ label: "Upload files", filesLimit: 2 });
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
await fileUpload.selectFile(file3);
fileUpload.shouldHaveError("You can upload up to 2 files");
fileUpload.shouldNotHaveFile("file3.txt");
});
(0, vitest_1.test)("clears the global error after a file is removed", async () => {
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const file3 = createMockFile("file3.txt");
const fileUpload = renderFileUpload({ label: "Upload files", filesLimit: 2 });
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
await fileUpload.selectFile(file3);
fileUpload.shouldHaveError("You can upload up to 2 files");
await fileUpload.clickRemoveButton("file1.txt");
fileUpload.shouldNotHaveError("You can upload up to 2 files");
});
});
(0, vitest_1.describe)("validation messages", () => {
(0, vitest_1.test)("renders a per-file message with a danger border for files carrying one", () => {
const file = createMockFile("invalid.txt");
const fileUpload = renderFileUpload({ initialValue: [{ file, validationMessage: "Per-file error here" }] });
fileUpload.shouldHaveFileLevelError("invalid.txt", "Per-file error here");
});
(0, vitest_1.test)("renders the component-level validationMessage as an error", () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({ initialValue: [{ file }], validationMessage: "Something went wrong" });
fileUpload.shouldHaveError("Something went wrong");
});
(0, vitest_1.test)("renders the component-level validationMessage with a spinner while validating", () => {
const fileUpload = renderFileUpload({ validationState: "validating", validationMessage: "Validation in progress" });
fileUpload.shouldHaveValidatingMessage("Validation in progress");
});
});
(0, vitest_1.describe)("default, required and optional marker properties", () => {
(0, vitest_1.test)("renders with required marker when hasRequiredMarker is true", () => {
const fileUpload = renderFileUpload({ hasRequiredMarker: true });
fileUpload.shouldHaveRequiredMarker();
});
(0, vitest_1.test)("renders with optional marker when hasOptionalMarker is true", () => {
const fileUpload = renderFileUpload({ hasOptionalMarker: true });
fileUpload.shouldHaveOptionalMarker();
});
(0, vitest_1.test)("renders with default marker when hasDefaultMarker is true", () => {
const fileUpload = renderFileUpload({ hasDefaultMarker: true });
fileUpload.shouldHaveDefaultMarker();
});
});
(0, vitest_1.describe)("accessibility attributes", () => {
(0, vitest_1.test)("describes the input by the global message when one is present", () => {
const fileUpload = renderFileUpload({ validationMessage: "Something went wrong" });
fileUpload.shouldHaveAriaDescribedByGlobalMessage();
});
(0, vitest_1.test)("describes the input by the description when one is present", () => {
const fileUpload = renderFileUpload({ description: "Help text" });
fileUpload.shouldHaveAriaDescribedByDescription();
});
(0, vitest_1.test)("describes the input by both the description and the global message", () => {
const fileUpload = renderFileUpload({ description: "Help text", validationMessage: "Something went wrong" });
fileUpload.shouldHaveAriaDescribedByBoth();
});
(0, vitest_1.test)("has aria-required when hasRequiredMarker is true", () => {
const fileUpload = renderFileUpload({ hasRequiredMarker: true });
fileUpload.shouldHaveAriaRequired(true);
});
(0, vitest_1.test)("groups each file row by the filename", () => {
const file1 = createMockFile("test1.txt");
const file2 = createMockFile("test2.txt");
const fileUpload = renderFileUpload({ label: "Upload file", filesLimit: 2, initialValue: [{ file: file1 }, { file: file2 }] });
fileUpload.shouldHaveGroupForFileRow("test1.txt");
fileUpload.shouldHaveGroupForFileRow("test2.txt");
});
(0, vitest_1.test)("marks a file row invalid and describes it when the file has a validation message", () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({ initialValue: [{ file, validationMessage: "Per-file error" }] });
fileUpload.shouldHaveAriaInvalidAndDescribedByForFileRow("test.txt", "Per-file error");
});
});
(0, vitest_1.describe)("accepted file extensions", () => {
(0, vitest_1.test)("sets the accept attribute on the file input when acceptedFileTypes is provided", () => {
const fileUpload = renderFileUpload({ label: "Upload file", acceptedFileTypes: [".txt", ".pdf", ".doc"] });
fileUpload.shouldHaveAcceptAttribute(".txt,.pdf,.doc");
});
});
});
function createMockFile(name, size = 1024) {
const file = new File(["content"], name, { type: "text/plain" });
Object.defineProperty(file, "size", { value: size });
return file;
}
function renderFileUpload({ label = "Upload file", initialValue = [], onChange = vitest_1.vi.fn(), ...rest } = {}) {
// FileUpload is controlled, so the harness owns the value and feeds each change back into the render.
function ControlledFileUpload() {
const [value, setValue] = (0, react_2.useState)(initialValue);
return ((0, jsx_runtime_1.jsx)(FileUpload_1.FileUpload, { label: label, value: value, onChange: (next) => {
setValue(next);
onChange(next);
}, ...rest }));
}
(0, react_1.render)((0, jsx_runtime_1.jsx)(ControlledFileUpload, {}));
// applyAccept: false makes userEvent a transparent pipe (like a drag-drop) so the component's own
// validator performs the type check, rather than userEvent filtering by the input's accept attribute.
const user = user_event_1.default.setup({ applyAccept: false });
const getFileInput = (inputLabel = label) => react_1.screen.getByLabelText(new RegExp(inputLabel, "i"));
return {
selectFile: (files) => user.upload(getFileInput(), files),
clickRemoveButton: (fileName) => testing_library_1.domQueries.getButton(`Remove ${fileName}`).click(),
shouldBeInDocument: () => {
(0, vitest_1.expect)(getFileInput()).toBeInTheDocument();
},
shouldHaveLabel: (labelText) => {
(0, vitest_1.expect)(getFileInput(labelText)).toBeInTheDocument();
},
shouldHaveDescription: (description) => {
testing_library_1.domQueries.getText(description).assertIsInTheDocument();
},
shouldHaveError: (error) => {
testing_library_1.domQueries.getText(error).assertIsInTheDocument();
},
shouldNotHaveError: (error) => {
testing_library_1.domQueries.queryText(error).assertIsNotInTheDocument();
},
shouldHaveValidatingMessage: (message) => {
testing_library_1.domQueries.getText(message).assertIsInTheDocument();
},
shouldHaveRequiredMarker: () => {
testing_library_1.domQueries.getText("(required)").assertIsInTheDocument();
},
shouldHaveOptionalMarker: () => {
testing_library_1.domQueries.getText("(optional)").assertIsInTheDocument();
},
shouldHaveDefaultMarker: () => {
testing_library_1.domQueries.getText("(default)").assertIsInTheDocument();
},
shouldHaveFile: (fileName) => {
const fileList = testing_library_1.domQueries.getList("File list");
(0, vitest_1.expect)(fileList.getListItem(fileName)).toBeInTheDocument();
},
shouldNotHaveFile: (fileName) => {
const fileList = testing_library_1.domQueries.queryList("File list");
if (!fileList.innerElement) {
return;
}
(0, vitest_1.expect)((0, react_1.within)(fileList.innerElement).queryByText(fileName)).not.toBeInTheDocument();
},
shouldHaveFileLevelError: (fileName, error) => {
const listItem = getFileListItem(fileName);
(0, vitest_1.expect)((0, react_1.within)(listItem).getByText(error)).toBeInTheDocument();
},
shouldHaveEmittedFiles: (files) => {
(0, vitest_1.expect)(onChange).toHaveBeenLastCalledWith(files.map((file) => vitest_1.expect.objectContaining({ file })));
},
shouldHaveAriaDescribedByGlobalMessage: () => {
(0, vitest_1.expect)(getFileInput()).toHaveAttribute("aria-describedby", vitest_1.expect.stringContaining("validation"));
},
shouldHaveAriaDescribedByDescription: () => {
(0, vitest_1.expect)(getFileInput()).toHaveAttribute("aria-describedby", vitest_1.expect.stringContaining("description"));
},
shouldHaveAriaDescribedByBoth: () => {
const ariaDescribedBy = getFileInput().getAttribute("aria-describedby");
(0, vitest_1.expect)(ariaDescribedBy).toContain("description");
(0, vitest_1.expect)(ariaDescribedBy).toContain("validation");
},
shouldHaveAriaRequired: (required) => {
(0, vitest_1.expect)(getFileInput()).toHaveAttribute("aria-required", required.toString());
},
shouldHaveAcceptAttribute: (acceptValue) => {
(0, vitest_1.expect)(getFileInput()).toHaveAttribute("accept", acceptValue);
},
shouldHaveGroupForFileRow: (fileName) => {
const listItem = getFileListItem(fileName);
(0, vitest_1.expect)((0, react_1.within)(listItem).getByRole("group", { name: fileName })).toBeInTheDocument();
},
shouldHaveAriaInvalidAndDescribedByForFileRow: (fileName, errorMessage) => {
const listItem = getFileListItem(fileName);
const group = (0, react_1.within)(listItem).getByRole("group", { name: fileName });
(0, vitest_1.expect)(group).toHaveAttribute("aria-invalid", "true");
(0, vitest_1.expect)(group.getAttribute("aria-describedby")).toBeTruthy();
(0, vitest_1.expect)((0, react_1.within)(listItem).getByText(errorMessage)).toBeInTheDocument();
},
};
function getFileListItem(fileName) {
const listItem = testing_library_1.domQueries.getList("File list").getListItem(fileName);
if (!listItem) {
throw new Error(`File row for "${fileName}" not found in the file list.`);
}
return listItem;
}
}