UNPKG

@octopusdeploy/design-system-components

Version:
481 lines (480 loc) • 23.6 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 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(); }, }; }