UNPKG

@octopusdeploy/design-system-components

Version:
310 lines (309 loc) • 18 kB
"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; } }