diff --git a/README.md b/README.md
index d0c73fcb..814822d3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# skyflow-js
-Skyflow’s JavaScript SDK can be used to securely collect, tokenize, and reveal sensitive data in the browser without exposing your front-end infrastructure to sensitive data.
+Skyflow's JavaScript SDK can be used to securely collect, tokenize, and reveal sensitive data in the browser without exposing your front-end infrastructure to sensitive data.
---
@@ -2791,7 +2791,8 @@ Note:
### File upload limitations:
- Only non-executable file are allowed to be uploaded.
-- Files must have a maximum size of 32 MB
+- Files have a default maximum size of 32 MB per file. This limit is configurable using the `maxFileSize` option.
+- Up to 4 files can be uploaded at a time by default. This limit is configurable using the `maxFileCount` option.
- File columns can't enable tokenization, redaction, or arrays.
- Re-uploading a file overwrites previously uploaded data.
- Partial uploads or resuming a previous upload isn't supported.
@@ -2851,10 +2852,19 @@ element.uploadMultipleFiles();
Along with fileElementInput, you can define other options in the Options object as described below:
```js
const options = {
- allowedFileType: String[], // Optional, indicates the allowed file types for upload
+ allowedFileType: String[], // Optional. Restricts uploads to the listed file extensions (e.g. [".pdf", ".png"]).
+ blockEmptyFiles: Boolean, // Optional. When true, rejects files with 0 bytes. Default: false.
+ preserveFileName: Boolean, // Optional. When true, keeps the original filename on upload. Default: false.
+ maxFileSize: Number, // Optional. Maximum size in bytes for each individual file. Default: 32000000 (32 MB).
+ maxFileCount: Number, // Optional. Maximum number of files that can be selected at once. Must be a positive integer. Default: 4.
}
```
-`allowedFileType`: An array of string value that indicates the allowedFileTypes to be uploaded.
+
+- `allowedFileType`: An array of strings indicating which file extensions are accepted for upload.
+- `blockEmptyFiles`: When `true`, files with a size of 0 bytes are rejected.
+- `preserveFileName`: When `true`, the original filename is preserved on upload.
+- `maxFileSize`: Maximum allowed size **per file**, in bytes. If any file exceeds this limit, a validation error is shown with the filename. Defaults to `32000000` (32 MB). Only applies to `MULTI_FILE_INPUT` elements.
+- `maxFileCount`: Maximum number of files that can be selected for a single upload. Must be a positive integer. Defaults to `4`. Only applies to `MULTI_FILE_INPUT` elements.
#### File upload with options example
@@ -2889,8 +2899,10 @@ const cardNumberElement = collectContainer.create({
label: 'Card Number',
type: Skyflow.ElementType.CARD_NUMBER,
});
-const options = {
- allowedFileType: [".pdf",".png"];
+const options = {
+ allowedFileType: [".pdf", ".png"],
+ maxFileSize: 5000000, // 5 MB per file
+ maxFileCount: 3, // up to 3 files at once
};
const fileElement = collectContainer.create({
table: 'newTable',
diff --git a/package.json b/package.json
index aa5019e7..bd10b4db 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "skyflow-js",
"preferGlobal": true,
"analyze": false,
- "version": "2.7.7",
+ "version": "2.7.7-dev.a6816dc",
"author": "Skyflow",
"description": "Skyflow JavaScript SDK",
"homepage": "https://github.com/skyflowapi/skyflow-js",
diff --git a/samples/using-script-tag/composable-multi-file-upload.html b/samples/using-script-tag/composable-multi-file-upload.html
index 537ef2e2..ec2fb583 100644
--- a/samples/using-script-tag/composable-multi-file-upload.html
+++ b/samples/using-script-tag/composable-multi-file-upload.html
@@ -147,6 +147,8 @@
Collect Composable Elements
const options = {
allowedFileType: ["", ""],
blockEmptyFiles: true, // default is false, if set to true, it will block empty files
+ maxFileSize: 1000000, // default is 32000000 (32MB), if set, it will block files greater than the specified size
+ maxFileCount: 3, // default is 5, if set, it will block if the number of files exceeds the specified number
}
const fileElement = collectContainer.create({
diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts
index 62eb3769..d4c3543f 100644
--- a/src/core/internal/frame-element-init.ts
+++ b/src/core/internal/frame-element-init.ts
@@ -520,7 +520,12 @@ export default class FrameElementInit {
}
const files = state.value instanceof FileList ? Array.from(state.value) : [state.value];
- this.validateFiles(files, state, fileElement);
+ try {
+ this.validateFiles(files, state, fileElement);
+ } catch (err: any) {
+ rootReject({ errorResponse: [{ error: err?.error || err?.errors?.[0] || err }] });
+ return;
+ }
const uploadFile = (file: File, skyflowID?: string) => {
const formData = new FormData();
@@ -624,14 +629,19 @@ export default class FrameElementInit {
});
private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => {
+ if (files.length > fileElement.maxFileCount) {
+ throw new SkyflowError(
+ SKYFLOW_ERROR_CODE.FILE_COUNT_EXCEEDED,
+ [String(fileElement.maxFileCount)],
+ true,
+ );
+ }
files.forEach((file) => {
- // Check file validation
const validatedFileState = fileValidation(file, state.isRequired, fileElement);
if (!validatedFileState) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_TYPE, [], true);
}
- // Check filename validation
const isValidFileName = vaildateFileName(file.name);
if (!isValidFileName) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true);
@@ -700,8 +710,9 @@ export default class FrameElementInit {
type: error?.error?.type,
},
});
+ } else {
+ rootReject(error);
}
- rootReject(error);
});
});
diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts
index 1c2de7ba..889cf505 100644
--- a/src/core/internal/iframe-form/index.ts
+++ b/src/core/internal/iframe-form/index.ts
@@ -113,6 +113,10 @@ export default class IFrameFormElement extends EventEmitter {
blockEmptyFiles: boolean = false;
+ maxFileSize: number = 32_000_000;
+
+ maxFileCount: number = 4;
+
constructor(name: string, label: string, metaData: any, context: Context, skyflowID?: string) {
super();
const frameValues = name.split(':');
@@ -510,6 +514,7 @@ export default class IFrameFormElement extends EventEmitter {
validator(value: any) {
let resp = true;
let vaildateFileNames = true;
+ let fileSpecificError = '';
if (this.fieldType === ElementType.CARD_NUMBER && value) {
if (this.regex) {
@@ -537,16 +542,55 @@ export default class IFrameFormElement extends EventEmitter {
const files = this.state.value instanceof FileList
? Array.from(this.state.value)
: [this.state.value];
- for (let i = 0; i < files.length; i += 1) {
- try {
- resp = fileValidation(files[i], this.state.isRequired, {
- allowedFileType: this.allowedFileType,
- blockEmptyFiles: this.blockEmptyFiles,
- });
- } catch (err) {
- resp = false;
+ const sizeMBDisplay = `${Math.round(this.maxFileSize / 1_000_000)} MB`;
+ const countExceeded = files.length > this.maxFileCount;
+ const firstOversizedFile = files.find((f) => f.size > this.maxFileSize);
+
+ if (countExceeded && firstOversizedFile) {
+ resp = false;
+ fileSpecificError = parameterizedString(
+ logs.errorLogs.FILE_COUNT_AND_SIZE_EXCEEDED,
+ String(this.maxFileCount),
+ sizeMBDisplay,
+ );
+ } else if (countExceeded) {
+ resp = false;
+ fileSpecificError = parameterizedString(
+ logs.errorLogs.FILE_COUNT_EXCEEDED,
+ String(this.maxFileCount),
+ );
+ } else {
+ const oversizedFileNames: string[] = [];
+ for (let i = 0; i < files.length; i += 1) {
+ try {
+ const fileValid = fileValidation(files[i], this.state.isRequired, {
+ allowedFileType: this.allowedFileType,
+ blockEmptyFiles: this.blockEmptyFiles,
+ maxFileSize: this.maxFileSize,
+ });
+ if (!fileValid) resp = false;
+ } catch (err: any) {
+ resp = false;
+ if (files[i].size > this.maxFileSize) {
+ oversizedFileNames.push(files[i].name);
+ }
+ }
+ if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name);
+ }
+ if (oversizedFileNames.length > 0) {
+ if (files.length === 1) {
+ fileSpecificError = parameterizedString(
+ logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE,
+ sizeMBDisplay,
+ );
+ } else {
+ fileSpecificError = parameterizedString(
+ logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME,
+ oversizedFileNames.join(', '),
+ sizeMBDisplay,
+ );
+ }
}
- if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name);
}
} else {
// eslint-disable-next-line no-lonely-if
@@ -557,7 +601,9 @@ export default class IFrameFormElement extends EventEmitter {
if (!resp || !vaildateFileNames) {
this.isCustomValidationFailed = false;
if (!resp) {
- if (this.label) {
+ if (fileSpecificError) {
+ this.errorText = fileSpecificError;
+ } else if (this.label) {
this.errorText = `${parameterizedString(
logs.errorLogs.INVALID_COLLECT_VALUE_WITH_LABEL,
this.label,
diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts
index 22f8044e..3fda635c 100644
--- a/src/core/internal/index.ts
+++ b/src/core/internal/index.ts
@@ -116,6 +116,12 @@ export default class FrameElement {
if (Object.prototype.hasOwnProperty.call(options, 'blockEmptyFiles')) {
this.iFrameFormElement.blockEmptyFiles = options?.blockEmptyFiles;
}
+ if (Object.prototype.hasOwnProperty.call(options, 'maxFileSize')) {
+ this.iFrameFormElement.maxFileSize = options?.maxFileSize;
+ }
+ if (Object.prototype.hasOwnProperty.call(options, 'maxFileCount')) {
+ this.iFrameFormElement.maxFileCount = options?.maxFileCount;
+ }
}
// mount element onto dom
diff --git a/src/libs/element-options.ts b/src/libs/element-options.ts
index df162c82..be517865 100644
--- a/src/libs/element-options.ts
+++ b/src/libs/element-options.ts
@@ -423,10 +423,27 @@ export const formatOptions = (
if (Object.prototype.hasOwnProperty.call(options, 'blockEmptyFiles')) {
formattedOptions = {
...formattedOptions,
- blockEmptyFiles: formattedOptions.blockEmptyFiles,
+ blockEmptyFiles: options.blockEmptyFiles,
};
}
}
+ if (elementType === ELEMENTS.MULTI_FILE_INPUT.name) {
+ if (Object.prototype.hasOwnProperty.call(options, 'maxFileSize')) {
+ if (typeof options.maxFileSize !== 'number' || options.maxFileSize <= 0) {
+ throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS, ['maxFileSize'], true);
+ }
+ formattedOptions = { ...formattedOptions, maxFileSize: options.maxFileSize };
+ }
+ if (Object.prototype.hasOwnProperty.call(options, 'maxFileCount')) {
+ if (typeof options.maxFileCount !== 'number' || options.maxFileCount <= 0 || !Number.isInteger(options.maxFileCount)) {
+ throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS, ['maxFileCount'], true);
+ }
+ formattedOptions = { ...formattedOptions, maxFileCount: options.maxFileCount };
+ }
+ } else {
+ delete formattedOptions?.maxFileSize;
+ delete formattedOptions?.maxFileCount;
+ }
if (Object.prototype.hasOwnProperty.call(options, 'masking')) {
if (!validateBooleanOptions(options.masking)) {
diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts
index ecf72435..eb6d78b6 100644
--- a/src/utils/common/index.ts
+++ b/src/utils/common/index.ts
@@ -328,6 +328,8 @@ export interface CollectElementOptions {
preserveFileName?: boolean,
allowedFileType?: string[],
blockEmptyFiles?: boolean,
+ maxFileSize?: number,
+ maxFileCount?: number,
masking?: boolean,
maskingChar?: string,
}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index be4efe30..6396792f 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -396,6 +396,22 @@ const SKYFLOW_ERROR_CODE = {
code: 400,
description: logs.errorLogs.NO_FILE_SELECTED,
},
+ FILE_COUNT_EXCEEDED: {
+ code: 400,
+ description: logs.errorLogs.FILE_COUNT_EXCEEDED,
+ },
+ FILE_SIZE_EXCEEDED_SINGLE: {
+ code: 400,
+ description: logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE,
+ },
+ FILE_SIZE_EXCEEDED_WITH_NAME: {
+ code: 400,
+ description: logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME,
+ },
+ FILE_COUNT_AND_SIZE_EXCEEDED: {
+ code: 400,
+ description: logs.errorLogs.FILE_COUNT_AND_SIZE_EXCEEDED,
+ },
INVALID_TABLE_IN_UPSERT_OPTION: {
code: 400,
description: logs.errorLogs.INVALID_TABLE_IN_UPSERT_OPTION,
@@ -552,6 +568,10 @@ const SKYFLOW_ERROR_CODE = {
code: 400,
description: logs.errorLogs.INVALID_BOOLEAN_OPTIONS,
},
+ INVALID_POSITIVE_NUMBER_OPTIONS: {
+ code: 400,
+ description: logs.errorLogs.INVALID_POSITIVE_NUMBER_OPTIONS,
+ },
INVALID_MASKING_CHARACTER: {
code: 400,
description: logs.errorLogs.INVALID_MASKING_CHARACTER,
diff --git a/src/utils/helpers/index.ts b/src/utils/helpers/index.ts
index 58fa0a43..9d5b4db5 100644
--- a/src/utils/helpers/index.ts
+++ b/src/utils/helpers/index.ts
@@ -211,7 +211,10 @@ export const fileValidation = (value, required: Boolean = false, fileElement) =>
}
}
}
- if (value.size > 32000000) {
+ const sizeLimit = (Object.prototype.hasOwnProperty.call(fileElement, 'maxFileSize') && typeof fileElement.maxFileSize === 'number')
+ ? fileElement.maxFileSize
+ : 32_000_000;
+ if (value.size > sizeLimit) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_SIZE, [], true);
}
if (Object.prototype.hasOwnProperty.call(fileElement, 'blockEmptyFiles') && fileElement.blockEmptyFiles) {
diff --git a/src/utils/logs.ts b/src/utils/logs.ts
index 1d7be2aa..57583319 100644
--- a/src/utils/logs.ts
+++ b/src/utils/logs.ts
@@ -269,6 +269,10 @@ const logs = {
INVALID_FILE_TYPE: 'Invalid File Type.',
INVALID_FILE_SIZE: 'Invalid File Size',
NO_FILE_SELECTED: 'No File Selected',
+ FILE_COUNT_EXCEEDED: 'You can upload up to %s1 files. Remove file(s) to add more',
+ FILE_SIZE_EXCEEDED_SINGLE: 'File exceeds the %s1 size limit. Choose a smaller file',
+ FILE_SIZE_EXCEEDED_WITH_NAME: '%s1 exceeds the %s2 size limit. Remove it or choose a smaller file',
+ FILE_COUNT_AND_SIZE_EXCEEDED: 'You can upload up to %s1 files, each under %s2',
INVALID_UPSERT_OPTION_TYPE:
'Validation error. Invalid \'upsert\' key in insert options. Specify a value of type array instead',
EMPTY_UPSERT_OPTIONS_ARRAY:
@@ -317,6 +321,7 @@ const logs = {
INVALID_COMPOSABLE_CONTAINER_OPTIONS: 'Mount failed. Invalid options object. Specify a valid options object.',
COMPOSABLE_CONTAINER_NOT_MOUNTED: 'Mount elements first. Make sure all elements are mounted before calling \'collect\' on the container.',
INVALID_BOOLEAN_OPTIONS: 'Validation error. Invalid %s1 found in collect options. Specify a value of type boolean instead.',
+ INVALID_POSITIVE_NUMBER_OPTIONS: 'Validation error. Invalid %s1 found in collect options. Specify a positive number instead.',
INVALID_MASKING_CHARACTER: 'Validation error. Invalid masking character. Specify a valid masking character. ',
INVALID_INPUT_OPTIONS_FORMAT: 'Mount failed. Format must be a non-empty string. Specify a valid format.',
INVALID_INPUT_OPTIONS_TRANSLATION: 'Mount failed. Translation must be a non-empty object. Specify a valid translation.',
diff --git a/tests/core/internal/frame-element-init.additional.test.js b/tests/core/internal/frame-element-init.additional.test.js
index 58b4636b..a2b1f89a 100644
--- a/tests/core/internal/frame-element-init.additional.test.js
+++ b/tests/core/internal/frame-element-init.additional.test.js
@@ -39,6 +39,8 @@ jest.mock('../../../src/core-utils/collect', () => {
import FrameElementInit from '../../../src/core/internal/frame-element-init';
import SkyflowError from '../../../src/libs/skyflow-error';
import { ELEMENTS } from '../../../src/core/constants';
+import logs from '../../../src/utils/logs';
+import { parameterizedString } from '../../../src/utils/logs-helper';
import * as helpers from '../../../src/utils/helpers';
import Client, { mockClientRequest } from '../../../src/client';
import { ELEMENT_EVENTS_TO_IFRAME, COLLECT_TYPES } from '../../../src/core/constants';
@@ -61,8 +63,24 @@ const makeFile = (name = 'test.txt', size = 10, type = 'text/plain') => {
return new File([content], name, { type });
};
+// Builds a FileList-like object that passes `instanceof FileList` (DataTransfer unavailable in jsdom)
+const makeFileList = (...files) => {
+ const arr = [...files];
+ Object.defineProperty(arr, 'item', { value: (i) => arr[i] });
+ if (typeof FileList !== 'undefined') Object.setPrototypeOf(arr, FileList.prototype);
+ return arr;
+};
+
// Minimal stub for an iframe form element expected by FrameElementInit internals
-const makeFileElement = ({ multiple = false, files, name = 'upload', tableName = 'files_table', preserveFileName = true }) => {
+const makeFileElement = ({
+ multiple = false,
+ files,
+ name = 'upload',
+ tableName = 'files_table',
+ preserveFileName = true,
+ maxFileCount = 4,
+ maxFileSize = 32_000_000,
+}) => {
const value = multiple ? files : files[0];
return {
state: {
@@ -73,6 +91,8 @@ const makeFileElement = ({ multiple = false, files, name = 'upload', tableName =
tableName,
onFocusChange: jest.fn(),
preserveFileName,
+ maxFileCount,
+ maxFileSize,
fieldType: multiple ? ELEMENTS.MULTI_FILE_INPUT.name : ELEMENTS.FILE_INPUT.name,
iFrameName: `element:${multiple ? 'MULTI' : 'SINGLE'}_FILE_INPUT:123`,
};
@@ -267,7 +287,7 @@ describe('FrameElementInit extended unit tests', () => {
await expect(instance['multipleUploadFiles'](fileElement, config, { meta: 'x' })).rejects.toEqual({ error: 'No skyflow IDs returned from insert data' });
});
- test('multipleUploadFiles filename validation failure', async () => {
+ test('multipleUploadFiles filename validation failure returns errorResponse format', async () => {
const instance = new FrameElementInit();
const files = [makeFile('bad.txt')];
const fileElement = makeFileElement({ multiple: true, files });
@@ -275,7 +295,87 @@ describe('FrameElementInit extended unit tests', () => {
helpers.fileValidation = jest.fn(() => true);
helpers.vaildateFileName = jest.fn(() => false); // force invalid name
const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
- await expect(instance['multipleUploadFiles'](fileElement, config, {})).rejects.toBeTruthy();
+ const err = await instance['multipleUploadFiles'](fileElement, config, {}).catch(e => e);
+ expect(err).toHaveProperty('errorResponse');
+ expect(err.errorResponse).toHaveLength(1);
+ expect(err.errorResponse[0]).toHaveProperty('error');
+ });
+
+ test('multipleUploadFiles rejects with errorResponse format when file size validation fails', async () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('large.pdf', 6000000)];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileSize: 5000000 });
+ fileElement.state.value = files;
+ instance.iframeFormList = [fileElement];
+ const sizeError = new SkyflowError({ code: 400, description: 'Invalid File Size' }, [], true);
+ helpers.fileValidation = jest.fn(() => { throw sizeError; });
+ helpers.vaildateFileName = jest.fn(() => true);
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ await expect(instance['multipleUploadFiles'](fileElement, config, undefined))
+ .rejects.toEqual({ errorResponse: [{ error: { code: 400, description: 'Invalid File Size' } }] });
+ });
+
+ test('multipleUploadFiles rejects with errorResponse format when file count exceeded', async () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 2 });
+ fileElement.state.value = makeFileList(...files); // FileList so Array.from() is used, preserving count
+ instance.iframeFormList = [fileElement];
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ const err = await instance['multipleUploadFiles'](fileElement, config, undefined).catch(e => e);
+ expect(err).toHaveProperty('errorResponse');
+ expect(err.errorResponse).toHaveLength(1);
+ expect(err.errorResponse[0].error).toMatchObject({ code: 400 });
+ });
+
+ test('multipleUploadFiles rejects with { error: "No files selected" } when state.value is empty', async () => {
+ const instance = new FrameElementInit();
+ const fileElement = makeFileElement({ multiple: true, files: [makeFile('a.txt')] });
+ fileElement.state.value = '';
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ await expect(instance['multipleUploadFiles'](fileElement, config, undefined))
+ .rejects.toEqual({ error: 'No files selected' });
+ });
+
+ test('multipleUploadFiles rejects with { error: "No files selected" } when state.value is null', async () => {
+ const instance = new FrameElementInit();
+ const fileElement = makeFileElement({ multiple: true, files: [makeFile('a.txt')] });
+ fileElement.state.value = null;
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ await expect(instance['multipleUploadFiles'](fileElement, config, undefined))
+ .rejects.toEqual({ error: 'No files selected' });
+ });
+
+ test('multipleUploadFiles errorResponse contains error when SkyflowError has .errors[] (plural)', async () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('test.txt')];
+ const fileElement = makeFileElement({ multiple: true, files });
+ fileElement.state.value = files;
+ const pluralError = new SkyflowError({ code: 400, description: 'Multiple issues' }, [], false);
+ helpers.fileValidation = jest.fn(() => { throw pluralError; });
+ helpers.vaildateFileName = jest.fn(() => true);
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ const err = await instance['multipleUploadFiles'](fileElement, config, undefined).catch(e => e);
+ expect(err).toHaveProperty('errorResponse');
+ expect(err.errorResponse[0].error).toBeDefined();
+ });
+
+ test('multipleUploadFiles rejects with errorResponse format when validation fails in metaData branch', async () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 2 });
+ fileElement.state.value = makeFileList(...files); // FileList so Array.from() is used, preserving count
+ instance.iframeFormList = [fileElement];
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' };
+ const err = await instance['multipleUploadFiles'](fileElement, config, { meta: 'x' }).catch(e => e);
+ expect(err).toHaveProperty('errorResponse');
+ expect(err.errorResponse[0].error).toMatchObject({ code: 400 });
+ // insertDataCallInMultiFiles must NOT have been called since validation failed first
+ expect(mockClientRequest).not.toHaveBeenCalled();
});
// ===== multipleUploadFiles else branch (no metaData) coverage lines ~548-587 =====
@@ -324,6 +424,92 @@ describe('FrameElementInit extended unit tests', () => {
expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).toThrow(SkyflowError);
});
+ test('validateFiles throws FILE_COUNT_EXCEEDED when file count exceeds maxFileCount', () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 2 });
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).toThrow(SkyflowError);
+ });
+
+ test('validateFiles FILE_COUNT_EXCEEDED error message contains the configured maxFileCount', () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 2 });
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ let caughtError;
+ try {
+ instance['validateFiles'](files, fileElement.state, fileElement);
+ } catch (err) {
+ caughtError = err;
+ }
+ expect(caughtError).toBeInstanceOf(SkyflowError);
+ expect(caughtError.error.description).toBe(
+ parameterizedString(logs.errorLogs.FILE_COUNT_EXCEEDED, '2'),
+ );
+ });
+
+ test('validateFiles FILE_COUNT_EXCEEDED message reflects a different configured maxFileCount', () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt'), makeFile('d.txt'), makeFile('e.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 3 });
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ let caughtError;
+ try {
+ instance['validateFiles'](files, fileElement.state, fileElement);
+ } catch (err) {
+ caughtError = err;
+ }
+ expect(caughtError.error.description).toBe(
+ parameterizedString(logs.errorLogs.FILE_COUNT_EXCEEDED, '3'),
+ );
+ });
+
+ test('validateFiles passes when file count equals maxFileCount', () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt')];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 2 });
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).not.toThrow();
+ });
+
+ test('validateFiles passes when file count is within default maxFileCount of 4', () => {
+ const instance = new FrameElementInit();
+ const files = [makeFile('a.txt'), makeFile('b.txt'), makeFile('c.txt'), makeFile('d.txt')];
+ const fileElement = makeFileElement({ multiple: true, files });
+ helpers.fileValidation = jest.fn(() => true);
+ helpers.vaildateFileName = jest.fn(() => true);
+ expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).not.toThrow();
+ });
+
+ test('validateFiles with maxFileCount=1 and single oversized file throws size error not count error', () => {
+ const instance = new FrameElementInit();
+ const largeFile = makeFile('large.pdf', 6000000);
+ const files = [largeFile]; // 1 file — does NOT exceed maxFileCount=1
+ const fileElement = makeFileElement({ multiple: true, files, maxFileCount: 1, maxFileSize: 5000000 });
+ helpers.fileValidation = jest.fn(() => {
+ throw new SkyflowError({ code: 400, description: 'Invalid File Size' }, [], true);
+ });
+ helpers.vaildateFileName = jest.fn(() => true);
+ // Should throw the size error (from fileValidation), NOT the count error
+ expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).toThrow(SkyflowError);
+ expect(helpers.fileValidation).toHaveBeenCalledTimes(1);
+ });
+
+ test('validateFiles throws when single file exceeds maxFileSize', () => {
+ const instance = new FrameElementInit();
+ const largeFile = makeFile('large.pdf', 5000001);
+ const files = [largeFile];
+ const fileElement = makeFileElement({ multiple: true, files, maxFileSize: 5000000 });
+ helpers.fileValidation = jest.fn(() => { throw new SkyflowError({ code: 400, description: 'Invalid File Size' }, [], true); });
+ helpers.vaildateFileName = jest.fn(() => true);
+ expect(() => instance['validateFiles'](files, fileElement.state, fileElement)).toThrow(SkyflowError);
+ });
+
test('parallelUploadFiles resolves with aggregated responses when all succeed', async () => {
const instance = new FrameElementInit();
const fileElementA = makeFileElement({ multiple: false, files: [makeFile('a.txt')] });
diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js
index 339b8a2d..ed7f65df 100644
--- a/tests/core/internal/iframe-form/iframe-form.test.js
+++ b/tests/core/internal/iframe-form/iframe-form.test.js
@@ -1159,3 +1159,152 @@ describe('getFileDetails isolated tests', () => {
expect(details).toEqual([]);
});
});
+
+describe('MULTI_FILE_INPUT validator - specific UI error messages', () => {
+ // Builds a FileList-like object (DataTransfer is unavailable in this jsdom version)
+ const makeFileList = (...files) => {
+ const arr = [...files];
+ Object.defineProperty(arr, 'item', { value: (i) => arr[i] });
+ if (typeof FileList !== 'undefined') Object.setPrototypeOf(arr, FileList.prototype);
+ return arr;
+ };
+
+ test('invalid file type in MULTI_FILE_INPUT shows generic error not size-specific message', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileSize = 10_000_000; // 10 MB — file below this so size is not the issue
+ element.allowedFileType = ['.pdf'];
+ // .zip is not in allowedFileType — fileValidation throws INVALID_FILE_TYPE
+ const file = { name: 'archive.zip', size: 100, type: 'application/zip' };
+ element.state.value = file;
+ const result = element.validator(file);
+ expect(result).toBe(false);
+ // fileSpecificError must NOT be set — size was fine, so no size message
+ expect(element.errorText).not.toContain('size limit');
+ });
+
+ test('default maxFileSize is 32 MB when no option is provided', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ expect(element.maxFileSize).toBe(32_000_000);
+ });
+
+ test('default maxFileCount is 4 when no option is provided', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ expect(element.maxFileCount).toBe(4);
+ });
+
+ test('maxFileCount=1 with single oversized file gives size error, not count error', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileCount = 1;
+ element.maxFileSize = 5_000_000; // 5 MB
+ const file = { name: 'large.pdf', size: 6_000_000, type: 'application/pdf' };
+ element.state.value = file;
+ const result = element.validator(file);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE, '5 MB'),
+ );
+ });
+
+ test('single file exceeding configured maxFileSize shows FILE_SIZE_EXCEEDED_SINGLE with correct size', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileSize = 5_000_000; // 5 MB
+ const file = { name: 'report.pdf', size: 5_000_001, type: 'application/pdf' };
+ element.state.value = file;
+ const result = element.validator(file);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE, '5 MB'),
+ );
+ });
+
+ test('one of multiple files too large shows FILE_SIZE_EXCEEDED_WITH_NAME with filename and configured size', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileSize = 3; // 3 bytes — keep files tiny for test speed
+ const okFile = new File(['ok'], 'small.pdf', { type: 'application/pdf' }); // 2 bytes — within limit
+ const bigFile = new File(['toobig!'], 'bigfile.pdf', { type: 'application/pdf' }); // 7 bytes — over limit
+ const fileList = makeFileList(okFile, bigFile);
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME, 'bigfile.pdf', '0 MB'),
+ );
+ });
+
+ test('oversized file first followed by valid file still reports error (order-independent)', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileSize = 3; // 3 bytes limit
+ const bigFile = new File(['toobig!'], 'bigfile.pdf', { type: 'application/pdf' }); // 7 bytes — over limit
+ const okFile = new File(['ok'], 'small.pdf', { type: 'application/pdf' }); // 2 bytes — within limit
+ const fileList = makeFileList(bigFile, okFile); // oversized file is first
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME, 'bigfile.pdf', '0 MB'),
+ );
+ });
+
+ test('multiple oversized files shows all filenames joined in error message', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileSize = 3; // 3 bytes limit
+ const bigFile1 = new File(['toobig!'], 'large1.pdf', { type: 'application/pdf' }); // 7 bytes — over limit
+ const bigFile2 = new File(['alsobig'], 'large2.pdf', { type: 'application/pdf' }); // 7 bytes — over limit
+ const okFile = new File(['ok'], 'small.pdf', { type: 'application/pdf' }); // 2 bytes — within limit
+ const fileList = makeFileList(bigFile1, okFile, bigFile2);
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME, 'large1.pdf, large2.pdf', '0 MB'),
+ );
+ });
+
+ test('file count exceeds configured maxFileCount shows FILE_COUNT_EXCEEDED with that count', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileCount = 2; // configurable — not the default 4
+ const fileList = makeFileList(
+ new File(['a'], 'f1.pdf', { type: 'application/pdf' }),
+ new File(['b'], 'f2.pdf', { type: 'application/pdf' }),
+ new File(['c'], 'f3.pdf', { type: 'application/pdf' }),
+ );
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_COUNT_EXCEEDED, '2'),
+ );
+ });
+
+ test('FILE_COUNT_EXCEEDED message changes when maxFileCount is different (e.g. 6)', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileCount = 6;
+ const files = Array.from({ length: 7 }, (_, i) =>
+ new File(['x'], `f${i + 1}.pdf`, { type: 'application/pdf' }),
+ );
+ const fileList = makeFileList(...files);
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_COUNT_EXCEEDED, '6'),
+ );
+ });
+
+ test('count and size both exceeded shows FILE_COUNT_AND_SIZE_EXCEEDED with configured count and size', () => {
+ const element = new IFrameFormElement(multi_file_element, '', { containerType: ContainerType.COLLECT }, context);
+ element.maxFileCount = 2;
+ element.maxFileSize = 3; // 3 bytes
+ const fileList = makeFileList(
+ new File(['toobig1'], 'f1.pdf', { type: 'application/pdf' }), // 7 bytes > 3
+ new File(['toobig2'], 'f2.pdf', { type: 'application/pdf' }), // 7 bytes > 3
+ new File(['toobig3'], 'f3.pdf', { type: 'application/pdf' }), // 7 bytes > 3
+ );
+ element.state.value = fileList;
+ const result = element.validator(fileList);
+ expect(result).toBe(false);
+ expect(element.errorText).toBe(
+ parameterizedString(logs.errorLogs.FILE_COUNT_AND_SIZE_EXCEEDED, '2', '0 MB'),
+ );
+ });
+});
diff --git a/tests/core/internal/internal-index.test.js b/tests/core/internal/internal-index.test.js
index 93496ce2..0b3a0fdc 100644
--- a/tests/core/internal/internal-index.test.js
+++ b/tests/core/internal/internal-index.test.js
@@ -2760,4 +2760,82 @@ describe('setDropdownIconStyle Tests', () => {
expect(frameElement.dropdownIcon.style.display).toBe('block');
});
+});
+
+describe('FrameElement constructor - maxFileSize and maxFileCount options', () => {
+ let mockIFrameFormElement;
+ let mockHtmlDivElement;
+
+ beforeEach(() => {
+ jest.spyOn(bus, 'target').mockReturnValue({ on: jest.fn(), emit: jest.fn() });
+ jest.spyOn(bus, 'on');
+ mockIFrameFormElement = {
+ resetEvents: jest.fn(),
+ on: jest.fn(),
+ setValue: jest.fn(),
+ setMask: jest.fn(),
+ setValidation: jest.fn(),
+ setReplacePattern: jest.fn(),
+ setFormat: jest.fn(),
+ getStatus: jest.fn().mockReturnValue({
+ isFocused: false, isValid: true, isEmpty: true,
+ isComplete: false, isRequired: false, isTouched: false, value: '',
+ }),
+ getValue: jest.fn().mockReturnValue(''),
+ getUnformattedValue: jest.fn().mockReturnValue(''),
+ onFocusChange: jest.fn(),
+ onDropdownSelect: jest.fn(),
+ fieldType: ELEMENTS.MULTI_FILE_INPUT.name,
+ iFrameName: 'mockMultiFileFrame',
+ cardType: 'DEFAULT',
+ state: { value: undefined, isFocused: false, isValid: false, isEmpty: true, isComplete: false, name: '', isRequired: false, isTouched: false },
+ mask: [],
+ replacePattern: '',
+ maxFileSize: 32_000_000,
+ maxFileCount: 4,
+ };
+ mockHtmlDivElement = document.createElement('div');
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('sets maxFileSize on iFrameFormElement when option is provided', () => {
+ const options = { column: 'file', table: 'files', maxFileSize: 5_000_000 };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileSize).toBe(5_000_000);
+ });
+
+ it('does not overwrite maxFileSize when option is absent', () => {
+ const options = { column: 'file', table: 'files' };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileSize).toBe(32_000_000);
+ });
+
+ it('sets maxFileCount on iFrameFormElement when option is provided', () => {
+ const options = { column: 'file', table: 'files', maxFileCount: 2 };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileCount).toBe(2);
+ });
+
+ it('does not overwrite maxFileCount when option is absent', () => {
+ const options = { column: 'file', table: 'files' };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileCount).toBe(4);
+ });
+
+ it('sets both maxFileSize and maxFileCount together when both are provided', () => {
+ const options = { column: 'file', table: 'files', maxFileSize: 10_000_000, maxFileCount: 3 };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileSize).toBe(10_000_000);
+ expect(mockIFrameFormElement.maxFileCount).toBe(3);
+ });
+
+ it('sets maxFileSize to 0 when explicitly passed as 0 (hasOwnProperty check, not falsy check)', () => {
+ const options = { column: 'file', table: 'files', maxFileSize: 0 };
+ new FrameElement(mockIFrameFormElement, options, mockHtmlDivElement);
+ expect(mockIFrameFormElement.maxFileSize).toBe(0);
+ });
});
\ No newline at end of file
diff --git a/tests/libs/element-options.test.js b/tests/libs/element-options.test.js
index 8e254b1f..f1673fd2 100644
--- a/tests/libs/element-options.test.js
+++ b/tests/libs/element-options.test.js
@@ -193,6 +193,85 @@ describe('test formatOptions function with format and translation', () => {
expect(options).toEqual({cardMetadata:{scheme:[CardType.VISA,CardType.CARTES_BANCAIRES]}, "cardSeperator": " ","enableCardIcon": true,"required": false,})
});
+ test('should include maxFileSize in formatted options for MULTI_FILE_INPUT', () => {
+ const formattedOptions = formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileSize: 4000000 }, LogLevel.ERROR);
+ expect(formattedOptions.maxFileSize).toBe(4000000);
+ });
+
+ test('should throw error for maxFileSize provided as non-number for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileSize: 'large' }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileSize'));
+ done();
+ }
+ });
+
+ test('should throw error for maxFileSize provided as zero for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileSize: 0 }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileSize'));
+ done();
+ }
+ });
+
+ test('should throw error for maxFileSize provided as negative number for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileSize: -1000 }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileSize'));
+ done();
+ }
+ });
+
+ test('should include maxFileCount in formatted options for MULTI_FILE_INPUT', () => {
+ const formattedOptions = formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileCount: 2 }, LogLevel.ERROR);
+ expect(formattedOptions.maxFileCount).toBe(2);
+ });
+
+ test('should throw error for maxFileCount provided as non-integer for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileCount: 2.5 }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileCount'));
+ done();
+ }
+ });
+
+ test('should throw error for maxFileCount provided as zero for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileCount: 0 }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileCount'));
+ done();
+ }
+ });
+
+ test('should throw error for maxFileCount provided as negative number for MULTI_FILE_INPUT', (done) => {
+ try {
+ formatOptions(ElementType.MULTI_FILE_INPUT, { maxFileCount: -1 }, LogLevel.ERROR);
+ done('should throw error');
+ } catch (err) {
+ expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS.description, 'maxFileCount'));
+ done();
+ }
+ });
+
+ test('should not include maxFileSize in formatted options for FILE_INPUT', () => {
+ const formattedOptions = formatOptions(ElementType.FILE_INPUT, { maxFileSize: 4000000 }, LogLevel.ERROR);
+ expect(formattedOptions.maxFileSize).toBeUndefined();
+ });
+
+ test('should not include maxFileCount in formatted options for FILE_INPUT', () => {
+ const formattedOptions = formatOptions(ElementType.FILE_INPUT, { maxFileCount: 2 }, LogLevel.ERROR);
+ expect(formattedOptions.maxFileCount).toBeUndefined();
+ });
});
diff --git a/tests/utils/helpers.test.js b/tests/utils/helpers.test.js
index 73d63091..0466d85d 100644
--- a/tests/utils/helpers.test.js
+++ b/tests/utils/helpers.test.js
@@ -324,6 +324,63 @@ describe('test file validation', () => {
}
expect(fileValidation(file, false, {allowedFileType: ['image/jpeg']})).toBe(true);
})
+
+ test('valid file within custom maxFileSize limit', () => {
+ const file = {
+ name: "sample.pdf",
+ size: 3000000,
+ type: "application/pdf",
+ }
+ expect(fileValidation(file, false, { maxFileSize: 4000000 })).toBe(true);
+ })
+
+ test('invalid file exceeding custom maxFileSize limit', () => {
+ const file = {
+ name: "sample.pdf",
+ size: 5000000,
+ type: "application/pdf",
+ }
+ expect(() => {
+ fileValidation(file, false, { maxFileSize: 4000000 });
+ }).toThrowError(expect.objectContaining({
+ error: expect.objectContaining({
+ description: parameterizedString(SKYFLOW_ERROR_CODE.INVALID_FILE_SIZE.description)
+ })
+ }));
+ })
+
+ test('file at exact custom maxFileSize boundary is valid', () => {
+ const file = {
+ name: "sample.pdf",
+ size: 4000000,
+ type: "application/pdf",
+ }
+ expect(fileValidation(file, false, { maxFileSize: 4000000 })).toBe(true);
+ })
+
+ test('falls back to 32MB default when maxFileSize not provided: file just within limit', () => {
+ const file = {
+ name: "sample.pdf",
+ size: 32000000,
+ type: "application/pdf",
+ }
+ expect(fileValidation(file, false, {})).toBe(true);
+ })
+
+ test('falls back to 32MB default when maxFileSize not provided: file exceeds limit', () => {
+ const file = {
+ name: "sample.pdf",
+ size: 32000001,
+ type: "application/pdf",
+ }
+ expect(() => {
+ fileValidation(file, false, {});
+ }).toThrowError(expect.objectContaining({
+ error: expect.objectContaining({
+ description: parameterizedString(SKYFLOW_ERROR_CODE.INVALID_FILE_SIZE.description)
+ })
+ }));
+ })
})