@octopusdeploy/design-system-components
Version:
The design systems component library.
481 lines (480 loc) • 23.6 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 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)("does not render the global summary when only a single file is selected", async () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
validationMessages: new Map([[file, "This field is required"]]),
validationState: "error",
});
await fileUpload.selectFile(file);
fileUpload.shouldNotHaveError("1 of 1 files failed validation.");
fileUpload.shouldHaveFileLevelError("test.txt", "This field is required");
});
it("calls onChange when a file is selected", async () => {
const onChange = vitest_1.vi.fn();
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
});
await fileUpload.selectFile(file);
(0, vitest_1.expect)(onChange).toHaveBeenCalledWith([file]);
});
it("displays the uploaded file name", async () => {
const onChange = vitest_1.vi.fn();
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
});
await fileUpload.selectFile(file);
fileUpload.shouldHaveFile("test.txt");
});
it("removes a file when remove button is clicked", async () => {
const onChange = vitest_1.vi.fn();
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
});
await fileUpload.selectFile(file);
fileUpload.shouldHaveFile("test.txt");
await fileUpload.clickRemoveButton("test.txt");
fileUpload.shouldNotHaveFile("test.txt");
});
it("allows multiple files when filesLimit is greater than 1", async () => {
const onChange = vitest_1.vi.fn();
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 3,
});
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
fileUpload.shouldHaveFile("file1.txt");
fileUpload.shouldHaveFile("file2.txt");
});
it("shows error when file size exceeds limit", async () => {
const oneGbBytes = 1024 * 1024 * 1024;
const onChange = vitest_1.vi.fn();
const largeFile = createMockFile("test.txt", oneGbBytes + 1);
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
maxFileSizeBytes: oneGbBytes,
});
await fileUpload.selectFile(largeFile);
fileUpload.shouldHaveError("File test.txt is too big");
fileUpload.shouldHaveError("Size limit is 1 GB");
(0, vitest_1.expect)(onChange).not.toHaveBeenCalled();
});
it("shows error when files limit is exceeded", async () => {
const onChange = vitest_1.vi.fn();
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const file3 = createMockFile("file3.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 2,
});
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
onChange.mockClear();
await fileUpload.selectFile(file3);
fileUpload.shouldHaveError("Maximum number of files exceeded");
fileUpload.shouldHaveError("You can upload up to 2 files");
(0, vitest_1.expect)(onChange).not.toHaveBeenCalled();
});
it("replaces file when filesLimit is 1 and a new file is selected", async () => {
const onChange = vitest_1.vi.fn();
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
filesLimit: 1,
});
await fileUpload.selectFile(file1);
await fileUpload.selectFile(file2);
fileUpload.shouldNotHaveFile("file1.txt");
fileUpload.shouldHaveFile("file2.txt");
(0, vitest_1.expect)(onChange).toHaveBeenLastCalledWith([file2]);
});
it("shows error when selecting multiple valid files at once that exceed filesLimit", async () => {
const onChange = vitest_1.vi.fn();
const file1 = createMockFile("file1.txt");
const file2 = createMockFile("file2.txt");
const file3 = createMockFile("file3.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 2,
});
await fileUpload.selectFile([file1, file2, file3]);
fileUpload.shouldHaveError("Maximum number of files exceeded");
fileUpload.shouldNotHaveFile("file1.txt");
fileUpload.shouldNotHaveFile("file2.txt");
fileUpload.shouldNotHaveFile("file3.txt");
(0, vitest_1.expect)(onChange).not.toHaveBeenCalled();
});
it("clears internal error when a valid file is uploaded after an error", async () => {
const onChange = vitest_1.vi.fn();
const largeFile = createMockFile("big.zip", Math.pow(1024, 4));
const validFile = createMockFile("small.zip", 1024);
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
});
await fileUpload.selectFile(largeFile);
fileUpload.shouldHaveError("File big.zip is too big");
await fileUpload.selectFile(validFile);
fileUpload.shouldNotHaveError("File big.zip is too big");
(0, vitest_1.expect)(onChange).toHaveBeenCalledWith([validFile]);
});
(0, vitest_1.describe)("mixed valid and invalid files", () => {
(0, vitest_1.test)("shows error when accepted files would exceed filesLimit", async () => {
const onChange = vitest_1.vi.fn();
const existingFile = createMockFile("existing.txt");
const validFile1 = createMockFile("valid1.txt");
const validFile2 = createMockFile("valid2.txt");
const invalidFile = createMockFile("invalid.jpg");
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 2,
acceptedFileTypes: [".txt"],
});
await fileUpload.selectFile(existingFile);
fileUpload.shouldHaveFile("existing.txt");
onChange.mockClear();
await fileUpload.selectFile([validFile1, validFile2, invalidFile]);
fileUpload.shouldHaveError("Maximum number of files exceeded");
fileUpload.shouldHaveFile("existing.txt");
fileUpload.shouldNotHaveFile("valid1.txt");
fileUpload.shouldNotHaveFile("valid2.txt");
(0, vitest_1.expect)(onChange).not.toHaveBeenCalled();
});
(0, vitest_1.test)("accepts valid files when mixed with invalid files", async () => {
const onChange = vitest_1.vi.fn();
const validFile1 = createMockFile("valid1.txt", 500);
const validFile2 = createMockFile("valid2.txt", 700);
const tooLargeFile = createMockFile("toolarge.txt", 2000);
const maxFileSizeBytes = 1024;
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 5,
maxFileSizeBytes,
});
await fileUpload.selectFile([validFile1, validFile2, tooLargeFile]);
fileUpload.shouldHaveFile("valid1.txt");
fileUpload.shouldHaveFile("valid2.txt");
fileUpload.shouldNotHaveFile("toolarge.txt");
fileUpload.shouldHaveError("File toolarge.txt is too big");
(0, vitest_1.expect)(onChange).toHaveBeenCalledWith([validFile1, validFile2]);
});
(0, vitest_1.test)("shows error for rejected files when some files are accepted", async () => {
const onChange = vitest_1.vi.fn();
const validFile = createMockFile("valid.txt", 100);
const invalidFile = createMockFile("invalid.txt", 2000);
const maxFileSizeBytes = 1024;
const fileUpload = renderFileUpload({
label: "Upload files",
onChange,
filesLimit: 3,
maxFileSizeBytes,
});
await fileUpload.selectFile([validFile, invalidFile]);
fileUpload.shouldHaveFile("valid.txt");
fileUpload.shouldNotHaveFile("invalid.txt");
fileUpload.shouldHaveError("File invalid.txt is too big");
(0, vitest_1.expect)(onChange).toHaveBeenCalledWith([validFile]);
});
});
(0, vitest_1.describe)("accepted file extensions", () => {
(0, vitest_1.test)("sets accept attribute on file input when acceptedFileTypes is provided", () => {
const fileUpload = renderFileUpload({
label: "Upload file",
acceptedFileTypes: [".txt", ".pdf", ".doc"],
});
fileUpload.shouldHaveAcceptAttribute(".txt,.pdf,.doc");
});
(0, vitest_1.test)("accepts files that match accepted file types", async () => {
const onChange = vitest_1.vi.fn();
const validFile = createMockFile("document.txt");
const fileUpload = renderFileUpload({
label: "Upload file",
onChange,
acceptedFileTypes: [".txt", ".pdf"],
});
await fileUpload.selectFile(validFile);
fileUpload.shouldHaveFile("document.txt");
(0, vitest_1.expect)(onChange).toHaveBeenCalledWith([validFile]);
});
});
(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)("has correct accessibility attributes when global message is present", async () => {
const file1 = createMockFile("test.txt");
const file2 = createMockFile("test2.txt");
const fileUpload = renderFileUpload({
filesLimit: 5,
validationMessages: new Map([[file1, "Invalid selection"]]),
validationState: "error",
});
await fileUpload.selectFile([file1, file2]);
fileUpload.shouldHaveAriaDescribedByGlobalMessage();
});
(0, vitest_1.test)("has correct accessibility attributes when description is present", () => {
const description = "Help text";
const fileUpload = renderFileUpload({ description });
fileUpload.shouldHaveAriaDescribedByDescription();
});
(0, vitest_1.test)("has correct accessibility attributes when both description and error are present", async () => {
const description = "Help text";
const file1 = createMockFile("test1.txt");
const file2 = createMockFile("test2.txt");
const select = renderFileUpload({
description,
filesLimit: 5,
validationMessages: new Map([[file1, "Error message"]]),
validationState: "error",
});
await select.selectFile([file1, file2]);
select.shouldHaveAriaDescribedByBoth();
});
(0, vitest_1.test)("has aria-required when hasRequiredMarker is true", () => {
const fileUpload = renderFileUpload({ hasRequiredMarker: true });
fileUpload.shouldHaveAriaRequired(true);
});
(0, vitest_1.test)("group each file row entry by the filename", async () => {
const file1 = createMockFile("test1.txt");
const file2 = createMockFile("test2.txt");
const fileUpload = renderFileUpload({ label: "Upload file", filesLimit: 2 });
await fileUpload.selectFile([file1, file2]);
fileUpload.shouldHaveGroupForFileRow("test1.txt");
fileUpload.shouldHaveGroupForFileRow("test2.txt");
});
(0, vitest_1.test)("has correct accessibility attributes for file row when file validation message is present", async () => {
const file = createMockFile("test.txt");
const fileUpload = renderFileUpload({
validationMessages: new Map([[file, "Per-file error"]]),
validationState: "error",
});
await fileUpload.selectFile(file);
fileUpload.shouldHaveAriaInvalidAndDescribedByForFileRow("test.txt", "Per-file error");
});
});
(0, vitest_1.describe)("validation lifecycle", () => {
(0, vitest_1.test)("shows validating message and spinner when validationState is validating", () => {
const fileUpload = renderFileUpload({
validationState: "validating",
});
fileUpload.shouldHaveValidatingMessage("Validation in progress");
});
(0, vitest_1.test)("shows custom validating message when provided", () => {
const fileUpload = renderFileUpload({
validationState: "validating",
validatingMessage: "Scanning package contents",
});
fileUpload.shouldHaveValidatingMessage("Scanning package contents");
});
(0, vitest_1.test)("renders per-file message with danger border for files in the validationMessages map", async () => {
const file = createMockFile("invalid.txt");
const fileUpload = renderFileUpload({
validationMessages: new Map([[file, "Per-file error here"]]),
validationState: "error",
});
await fileUpload.selectFile(file);
fileUpload.shouldHaveFileLevelError("invalid.txt", "Per-file error here");
});
(0, vitest_1.test)("shows summary count when validationState is error with multiple files", async () => {
const file1 = createMockFile("a.txt");
const file2 = createMockFile("b.txt");
const file3 = createMockFile("c.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
filesLimit: 5,
validationMessages: new Map([
[file1, "Bad a"],
[file2, "Bad b"],
]),
validationState: "error",
});
await fileUpload.selectFile([file1, file2, file3]);
fileUpload.shouldHaveError("2 of 3 files failed validation.");
});
(0, vitest_1.test)("does not show summary count when validationState is idle", async () => {
const file1 = createMockFile("a.txt");
const file2 = createMockFile("b.txt");
const file3 = createMockFile("c.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
filesLimit: 5,
validationMessages: new Map([
[file1, "Bad a"],
[file2, "Bad b"],
]),
validationState: "idle",
});
await fileUpload.selectFile([file1, file2, file3]);
fileUpload.shouldNotHaveError("2 of 3 files failed validation.");
fileUpload.shouldHaveFileLevelError("a.txt", "Bad a");
fileUpload.shouldHaveFileLevelError("b.txt", "Bad b");
});
(0, vitest_1.test)("dropzone internal error still renders globally and takes priority over summary", async () => {
const file1 = createMockFile("a.txt");
const file2 = createMockFile("b.txt");
const file3 = createMockFile("c.txt");
const fileUpload = renderFileUpload({
label: "Upload files",
filesLimit: 2,
validationState: "error",
});
await fileUpload.selectFile([file1, file2, file3]);
fileUpload.shouldHaveError("Maximum number of files exceeded");
});
});
});
function createMockFile(name, size = 1024) {
const file = new File(["content"], name, { type: "text/plain" });
Object.defineProperty(file, "size", { value: size });
return file;
}
function renderFileUpload(props = {}) {
const { label = "Upload file", onChange, ...rest } = props;
(0, react_1.render)((0, jsx_runtime_1.jsx)(FileUpload_1.FileUpload, { label: label, onChange: onChange ?? vitest_1.vi.fn(), ...rest }));
const getFileInput = (inputLabel = label) => react_1.screen.getByLabelText(new RegExp(inputLabel, "i"));
return {
selectFile: async (files) => {
await user_event_1.default.upload(getFileInput(), files);
},
clickRemoveButton: (fileName) => {
return 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();
},
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");
const listItem = fileList.getListItem(fileName);
(0, vitest_1.expect)(listItem).toBeInTheDocument();
},
shouldHaveFileLevelError: (fileName, error) => {
const fileList = testing_library_1.domQueries.getList("File list");
const listItem = fileList.getListItem(fileName);
if (!listItem) {
throw new Error(`File row for "${fileName}" not found in the file list.`);
}
(0, vitest_1.expect)((0, react_1.within)(listItem).getByText(error)).toBeInTheDocument();
},
shouldHaveValidatingMessage: (message) => {
testing_library_1.domQueries.getText(message).assertIsInTheDocument();
},
shouldNotHaveFile: (fileName) => {
const fileList = testing_library_1.domQueries.queryList("File list");
if (!fileList.innerElement) {
return;
}
const listItem = (0, react_1.within)(fileList.innerElement).queryByText(fileName);
(0, vitest_1.expect)(listItem).not.toBeInTheDocument();
},
shouldHaveAriaDescribedByGlobalMessage: () => {
const ariaDescribedBy = getFileInput().getAttribute("aria-describedby");
(0, vitest_1.expect)(ariaDescribedBy).toContain("validation");
},
shouldHaveAriaDescribedByDescription: () => {
const ariaDescribedBy = getFileInput().getAttribute("aria-describedby");
(0, vitest_1.expect)(ariaDescribedBy).toContain("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 fileList = testing_library_1.domQueries.getList("File list");
const listItem = fileList.getListItem(fileName);
if (!listItem) {
throw new Error(`File row for "${fileName}" not found in the file list.`);
}
(0, vitest_1.expect)((0, react_1.within)(listItem).getByRole("group", { name: fileName })).toBeInTheDocument();
},
shouldHaveAriaInvalidAndDescribedByForFileRow: (fileName, errorMessage) => {
const fileList = testing_library_1.domQueries.getList("File list");
const listItem = fileList.getListItem(fileName);
if (!listItem) {
throw new Error(`File row for "${fileName}" not found in the file list.`);
}
const group = (0, react_1.within)(listItem).getByRole("group", { name: fileName });
(0, vitest_1.expect)(group).toHaveAttribute("aria-invalid", "true");
const describedById = group.getAttribute("aria-describedby");
(0, vitest_1.expect)(describedById).toBeTruthy();
(0, vitest_1.expect)((0, react_1.within)(listItem).getByText(errorMessage)).toBeInTheDocument();
},
};
}