Skip to content
230 changes: 230 additions & 0 deletions test/core/workflow/workflow-acrobat/action-binder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,22 @@ describe('ActionBinder', () => {
it('should match image/tiff for pdf-to-png', () => {
expect(actionBinder.isSameFileType('pdf-to-png', 'image/tiff')).to.be.true;
});

it('should return false for quiz-maker with application/pdf', () => {
expect(actionBinder.isSameFileType('quiz-maker', 'application/pdf')).to.be.false;
});

it('should return false for quiz-maker with image/jpeg', () => {
expect(actionBinder.isSameFileType('quiz-maker', 'image/jpeg')).to.be.false;
});

it('should return false for flashcard-maker with application/pdf', () => {
expect(actionBinder.isSameFileType('flashcard-maker', 'application/pdf')).to.be.false;
});

it('should return false for flashcard-maker with image/jpeg', () => {
expect(actionBinder.isSameFileType('flashcard-maker', 'image/jpeg')).to.be.false;
});
});

describe('validateFiles', () => {
Expand Down Expand Up @@ -988,6 +1004,36 @@ describe('ActionBinder', () => {
expect(result).to.be.true;
});

it('should handle redirect for returning user for quiz-maker', async () => {
actionBinder.workflowCfg.enabledFeatures = ['quiz-maker'];
localStorage.setItem('unity.user', 'test-user');
localStorage.setItem('quiz-maker_attempts', '2');

const cOpts = { payload: {} };
const filesData = { test: 'data' };
const result = await actionBinder.handleRedirect(cOpts, filesData);

expect(cOpts.payload.newUser).to.be.false;
expect(cOpts.payload.attempts).to.equal('2+');
expect(actionBinder.getRedirectUrl.calledWith(cOpts)).to.be.true;
expect(result).to.be.true;
});

it('should handle redirect for returning user for flashcard-maker', async () => {
actionBinder.workflowCfg.enabledFeatures = ['flashcard-maker'];
localStorage.setItem('unity.user', 'test-user');
localStorage.setItem('flashcard-maker_attempts', '2');

const cOpts = { payload: {} };
const filesData = { test: 'data' };
const result = await actionBinder.handleRedirect(cOpts, filesData);

expect(cOpts.payload.newUser).to.be.false;
expect(cOpts.payload.attempts).to.equal('2+');
expect(actionBinder.getRedirectUrl.calledWith(cOpts)).to.be.true;
expect(result).to.be.true;
});

it('should handle redirect with feedback for multi-file validation failure', async () => {
actionBinder.multiFileValidationFailure = true;
const cOpts = { payload: {} };
Expand Down Expand Up @@ -1211,6 +1257,52 @@ describe('ActionBinder', () => {
expect(actionBinder.handleMultiFileUpload.calledWith(validFile)).to.be.true;
expect(actionBinder.handleSingleFileUpload.called).to.be.false;
});

it('should handle verbs that require multi-file upload for quiz-maker', async () => {
actionBinder.workflowCfg = {
name: 'workflow-acrobat',
enabledFeatures: ['quiz-maker'],
targetCfg: { verbsWithoutMfuToSfuFallback: ['compress-pdf', 'quiz-maker'] },
};
const files = [
{ name: 'test1.pdf', type: 'application/pdf', size: 1048576 },
{ name: 'test2.pdf', type: 'application/pdf', size: 2097152 },
];
const validFile = [files[0]];
actionBinder.validateFiles.resolves({ isValid: true, validFiles: validFile });

await actionBinder.handleFileUpload(files);

expect(actionBinder.sanitizeFileName.calledTwice).to.be.true;
expect(actionBinder.filterFilesWithPdflite.called).to.be.true;
expect(actionBinder.validateFiles.called).to.be.true;
expect(actionBinder.initUploadHandler.called).to.be.true;
expect(actionBinder.handleMultiFileUpload.calledWith(validFile)).to.be.true;
expect(actionBinder.handleSingleFileUpload.called).to.be.false;
});

it('should handle verbs that require multi-file upload for flashcard-maker', async () => {
actionBinder.workflowCfg = {
name: 'workflow-acrobat',
enabledFeatures: ['flashcard-maker'],
targetCfg: { verbsWithoutMfuToSfuFallback: ['compress-pdf', 'flashcard-maker'] },
};
const files = [
{ name: 'test1.pdf', type: 'application/pdf', size: 1048576 },
{ name: 'test2.pdf', type: 'application/pdf', size: 2097152 },
];
const validFile = [files[0]];
actionBinder.validateFiles.resolves({ isValid: true, validFiles: validFile });

await actionBinder.handleFileUpload(files);

expect(actionBinder.sanitizeFileName.calledTwice).to.be.true;
expect(actionBinder.filterFilesWithPdflite.called).to.be.true;
expect(actionBinder.validateFiles.called).to.be.true;
expect(actionBinder.initUploadHandler.called).to.be.true;
expect(actionBinder.handleMultiFileUpload.calledWith(validFile)).to.be.true;
expect(actionBinder.handleSingleFileUpload.called).to.be.false;
});
});

describe('continueInApp', () => {
Expand Down Expand Up @@ -1406,6 +1498,64 @@ describe('ActionBinder', () => {
spy.restore();
});

it('should handle input change event with single file for quiz-maker', async () => {
const el = document.createElement('input');
el.type = 'file';
const addEventListenerSpy = sinon.spy(el, 'addEventListener');
const block = { querySelector: sinon.stub().returns(el) };
const actMap = { input: 'upload' };
const extractSpy = sinon.spy(actionBinder, 'extractFiles');
const spy = sinon.spy(actionBinder, 'acrobatActionMaps');

await actionBinder.initActionListeners(block, actMap);

const handler = addEventListenerSpy.getCalls().find((call) => call.args[0] === 'change').args[1];
actionBinder.signedOut = false;
actionBinder.tokenError = null;
actionBinder.workflowCfg.enabledFeatures = ['quiz-maker'];

const file = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
const event = { target: { files: [file], value: '' } };

await handler(event);

expect(extractSpy.called).to.be.true;
expect(spy.called).to.be.true;
expect(spy.firstCall.args).to.deep.equal(['upload', [file], file.size, 'change']);

spy.restore();
extractSpy.restore();
});

it('should handle input change event with single file for flashcard-maker', async () => {
const el = document.createElement('input');
el.type = 'file';
const addEventListenerSpy = sinon.spy(el, 'addEventListener');
const block = { querySelector: sinon.stub().returns(el) };
const actMap = { input: 'upload' };
const extractSpy = sinon.spy(actionBinder, 'extractFiles');
const spy = sinon.spy(actionBinder, 'acrobatActionMaps');

await actionBinder.initActionListeners(block, actMap);

const handler = addEventListenerSpy.getCalls().find((call) => call.args[0] === 'change').args[1];
actionBinder.signedOut = false;
actionBinder.tokenError = null;
actionBinder.workflowCfg.enabledFeatures = ['flashcard-maker'];

const file = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
const event = { target: { files: [file], value: '' } };

await handler(event);

expect(extractSpy.called).to.be.true;
expect(spy.called).to.be.true;
expect(spy.firstCall.args).to.deep.equal(['upload', [file], file.size, 'change']);

spy.restore();
extractSpy.restore();
});

it('should handle input element not found', async () => {
const block = { querySelector: sinon.stub().returns(null) };
const actMap = { 'nonexistent-input': 'upload' };
Expand Down Expand Up @@ -1612,6 +1762,46 @@ describe('ActionBinder', () => {
{ code: 'pre_upload_error_missing_verb_config' },
)).to.be.true;
});

it('should not dispatch error when enabledFeatures[0] is quiz-maker', async () => {
actionBinder.dispatchErrorToast.resetHistory();
actionBinder.processSingleFile = sinon.stub().resolves();
actionBinder.processHybrid = sinon.stub().resolves();
actionBinder.workflowCfg.enabledFeatures = ['quiz-maker'];
const validFiles = [
{ name: 'test.pdf', type: 'application/pdf', size: 1048576 },
];
const totalFileSize = validFiles.reduce((sum, file) => sum + file.size, 0);
await actionBinder.acrobatActionMaps('upload', validFiles, totalFileSize, 'test-event');
expect(actionBinder.dispatchErrorToast.neverCalledWith(
'error_generic',
500,
'Invalid or missing verb configuration on Unity',
false,
true,
{ code: 'pre_upload_error_missing_verb_config' },
)).to.be.true;
});

it('should not dispatch error when enabledFeatures[0] is flashcard-maker', async () => {
actionBinder.dispatchErrorToast.resetHistory();
actionBinder.processSingleFile = sinon.stub().resolves();
actionBinder.processHybrid = sinon.stub().resolves();
actionBinder.workflowCfg.enabledFeatures = ['flashcard-maker'];
const validFiles = [
{ name: 'test.pdf', type: 'application/pdf', size: 1048576 },
];
const totalFileSize = validFiles.reduce((sum, file) => sum + file.size, 0);
await actionBinder.acrobatActionMaps('upload', validFiles, totalFileSize, 'test-event');
expect(actionBinder.dispatchErrorToast.neverCalledWith(
'error_generic',
500,
'Invalid or missing verb configuration on Unity',
false,
true,
{ code: 'pre_upload_error_missing_verb_config' },
)).to.be.true;
});
});
});

Expand Down Expand Up @@ -1776,6 +1966,46 @@ describe('ActionBinder', () => {
localStorageStub.restore();
actionBinder.getRedirectUrl.restore();
});

it('should handle localStorage access error for quiz-maker', async () => {
actionBinder.workflowCfg.enabledFeatures = ['quiz-maker'];
const localStorageStub = sinon.stub(window.localStorage, 'getItem');
localStorageStub.throws(new Error('localStorage not available'));

const cOpts = { payload: {} };
const filesData = { type: 'application/pdf', size: 123, count: 1 };
sinon.stub(actionBinder, 'getRedirectUrl').resolves();
actionBinder.redirectUrl = 'https://test-redirect.com';

const result = await actionBinder.handleRedirect(cOpts, filesData);

expect(result).to.be.true;
expect(cOpts.payload.newUser).to.be.true;
expect(cOpts.payload.attempts).to.equal('1st');

localStorageStub.restore();
actionBinder.getRedirectUrl.restore();
});

it('should handle localStorage access error for flashcard-maker', async () => {
actionBinder.workflowCfg.enabledFeatures = ['flashcard-maker'];
const localStorageStub = sinon.stub(window.localStorage, 'getItem');
localStorageStub.throws(new Error('localStorage not available'));

const cOpts = { payload: {} };
const filesData = { type: 'application/pdf', size: 123, count: 1 };
sinon.stub(actionBinder, 'getRedirectUrl').resolves();
actionBinder.redirectUrl = 'https://test-redirect.com';

const result = await actionBinder.handleRedirect(cOpts, filesData);

expect(result).to.be.true;
expect(cOpts.payload.newUser).to.be.true;
expect(cOpts.payload.attempts).to.equal('1st');

localStorageStub.restore();
actionBinder.getRedirectUrl.restore();
});
});

describe('Experiment Data Integration', () => {
Expand Down
2 changes: 2 additions & 0 deletions unitylibs/core/workflow/workflow-acrobat/action-binder.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export default class ActionBinder {
'summarize-pdf': ['single', 'allowed-filetypes-pdf-word-ppt-txt', 'page-limit-600', 'max-filesize-100-mb'],
'pdf-ai': ['hybrid', 'allowed-filetypes-pdf-word-ppt-txt', 'page-limit-600', 'max-numfiles-10', 'max-filesize-100-mb'],
'heic-to-pdf': ['hybrid', 'allowed-filetypes-all', 'allowed-filetypes-heic', 'max-filesize-100-mb'],
'quiz-maker': ['hybrid', 'allowed-filetypes-study-spaces', 'page-limit-600', 'max-numfiles-100', 'max-filesize-100-mb'],
'flashcard-maker': ['hybrid', 'allowed-filetypes-study-spaces', 'page-limit-600', 'max-numfiles-100', 'max-filesize-100-mb'],
};

static ERROR_MAP = {
Expand Down
17 changes: 17 additions & 0 deletions unitylibs/core/workflow/workflow-acrobat/limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,22 @@
},
"allowed-filetypes-heic": {
"allowedFileTypes": ["image/heic"]
},
"allowed-filetypes-study-spaces": {
"allowedFileTypes": [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msexcel",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/x-vnd.oasis.opendocument.spreadsheet",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/rtf",
"text/rtf",
"text/plain",
"text/vtt"
]
}
}
48 changes: 32 additions & 16 deletions unitylibs/core/workflow/workflow-acrobat/target-config.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
{
"verb-widget": {
"selector": ".verb-wrapper",
"_defaults": {
"renderWidget": false,
"source": ".verb-wrapper .verb-container",
"target": ".verb-wrapper .verb-container",
"showSplashScreen": true,
"splashScreenConfig": {
"fragmentLink": "/dc-shared/fragments/shared-fragments/frictionless/splash-page/splashscreen",
"fragmentLink-acrobat": "/dc-shared/fragments/shared-fragments/frictionless/splash-page/splashscreen-acrobat",
"splashScreenParent": "body"
},
"domainMap": {
"acrobat": [
"^(stage\\.)?acrobat\\.adobe\\.com$",
"^.+--dc-frictionless--adobecom\\.aem\\.(page|live)$"
]
},
"actionMap": {
".verb-wrapper": "upload",
"#file-upload": "upload"
},
"uploadLimits": {
"HIGH_END": { "files": 10, "chunks": 10 },
"MID_RANGE": { "files": 5, "chunks": 10 },
Expand All @@ -28,8 +15,8 @@
"sendSplunkAnalytics": true,
"verbsWithoutMfuToSfuFallback": ["compress-pdf"],
"nonpdfMfuFeedbackScreenTypeNonpdf": ["combine-pdf"],
"nonpdfSfuProductScreen": ["word-to-pdf", "jpg-to-pdf", "ppt-to-pdf", "excel-to-pdf", "png-to-pdf", "createpdf", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "heic-to-pdf"],
"mfuUploadAllowed": ["combine-pdf", "rotate-pages", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai"],
"nonpdfSfuProductScreen": ["word-to-pdf", "jpg-to-pdf", "ppt-to-pdf", "excel-to-pdf", "png-to-pdf", "createpdf", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "heic-to-pdf", "quiz-maker", "flashcard-maker"],
"mfuUploadAllowed": ["combine-pdf", "rotate-pages", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "quiz-maker", "flashcard-maker"],
"mfuUploadOnlyPdfAllowed": ["combine-pdf"],
"experimentationOn": ["add-comment"],
"fetchApiConfig": {
Expand All @@ -55,5 +42,34 @@
}
}
}
},
"verb-widget": {
"selector": ".verb-wrapper",
"source": ".verb-wrapper .verb-container",
"target": ".verb-wrapper .verb-container",
"showSplashScreen": true,
"splashScreenConfig": {
"fragmentLink": "/dc-shared/fragments/shared-fragments/frictionless/splash-page/splashscreen",
"fragmentLink-acrobat": "/dc-shared/fragments/shared-fragments/frictionless/splash-page/splashscreen-acrobat",
"splashScreenParent": "body"
},
"actionMap": {
".verb-wrapper": "upload",
"#file-upload": "upload"
}
},
"study-marquee": {
"selector": ".foreground",
"source": ".foreground .study-marquee-container",
"target": ".foreground .study-marquee-container",
"showSplashScreen": true,
"splashScreenConfig": {
"fragmentLink": "/dc-shared/fragments/shared-fragments/frictionless/splash-page/students-splashscreen",
"splashScreenParent": "body"
},
"actionMap": {
".foreground": "upload",
"#file-upload": "upload"
}
}
}
Loading
Loading