Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions test/core/workflow/mocks/inline-action-authored.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export default `<div>
<div class="hero-marquee center con-block has-bg">
<div class="copy"></div>
</div>
<div class="unity workflow-inline-action product-firefly feature-remove-background widget-inline-action">
<div>
<div>
<p>
<picture>
<source type="image/webp" srcset="./media_mobile.jpg?width=750&amp;format=webply" media="(min-width: 600px)">
<source type="image/webp" srcset="./media_mobile.jpg?width=750&amp;format=webply">
<img src="./media_mobile.jpg?width=750&amp;format=jpg" alt="Hero mobile">
</picture>
<a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload your image
</p>
<p>Or tap here</p>
<p>File must be JPEG(JPG), PNG, or WEBP and up to 100MB.</p>
<p>Adobe <a href="https://www.adobe.com/legal/terms.html">Terms of Use</a></p>
</div>
<div>
<p>
<picture>
<img src="./media_tablet.jpg?width=750&amp;format=jpg" alt="Hero tablet">
</picture>
</p>
<p><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload your image</p>
<p>Or tap here</p>
<p>File must be JPEG(JPG), PNG, or WEBP and up to 100MB.</p>
<p>Adobe <a href="https://www.adobe.com/legal/terms.html">Terms of Use</a></p>
</div>
<div>
<p>
<picture>
<source type="image/webp" srcset="./media_desktop.jpg?width=2000&amp;format=webply" media="(min-width: 600px)">
<source type="image/webp" srcset="./media_desktop.jpg?width=750&amp;format=webply">
<img src="./media_desktop.jpg?width=750&amp;format=jpg" alt="Hero desktop">
</picture>
</p>
<p><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload your image</p>
<p>Or drag and drop here</p>
<p>File must be JPEG(JPG), PNG, or WEBP and up to 100MB.</p>
<p>Adobe <a href="https://www.adobe.com/legal/terms.html">Terms of Use</a></p>
</div>
</div>
<div><div><ul>
<li><span class="icon icon-error-filesize"></span>File size larger than 100MB</li>
<li><span class="icon icon-download"></span><a href="/creativecloud/animation/testdoc/unity/generate.svg">https://example.com/generate.svg</a>Download</li>
<li><span class="icon icon-aiPhotoEditor"></span><a href="/creativecloud/animation/testdoc/unity/generate.svg">https://main--cc--adobecom.aem.live/creativecloud/animation/testdoc/unity/generate.svg</a> Edit in Firefly</li>
<li><span class="icon icon-placeholder-nba"></span>Do more with this image.</li>
<li><span class="icon icon-operation-removeBackground"></span></li>
</ul></div></div>
<div><div>cgen</div><div>ff_source=adobe-com&amp;ff_campaign=ai-art-generator</div></div>
<div><div><ul>
<li><picture><img src="./nba_upscale.png" alt=""></picture> Upscale<br><span class="icon icon-nba-upscale"></span><br>Upscale prompt</li>
<li><picture><img src="./nba_remove.png" alt=""></picture> Remove<br><span class="icon icon-nba-remove"></span><br>Remove prompt</li>
<li><picture><img src="./nba_gen.png" alt=""></picture> Generate new background<br><span class="icon icon-nba-generate-new-bg"></span><br>Generate prompt</li>
<li><picture><img src="./nba_light.png" alt=""></picture> Studio lighting<br><span class="icon icon-nba-studio-lighting"></span><br>Lighting prompt</li>
</ul></div></div>
</div>
</div>`;
19 changes: 19 additions & 0 deletions test/core/workflow/mocks/inline-action-body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default `<div>
<div class="hero-marquee center con-block has-bg">
<div class="copy"></div>
</div>
<div class="unity workflow-inline-action product-firefly feature-remove-background widget-inline-action">
<div><div><p><picture><img src="http://localhost:2000/test/assets/media_.jpeg" alt="Hero"></picture><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">Upload icon</a> Upload your image</p><p>Or drag and drop here</p><p>File must be JPEG(JPG), PNG, or WEBP and up to 100MB.</p><p>By uploading your image, you agree to the Adobe <a href="https://www.adobe.com/legal/terms.html">Terms of Use</a>.</p></div></div>
<div><div><ul>
<li><span class="icon icon-error-filesize"></span>File size larger than 100MB</li>
<li><span class="icon icon-operation-removeBackground"></span></li>
<li><span class="icon icon-placeholder-loading"></span>Uploading image, loading remove background</li>
<li><span class="icon icon-placeholder-nba"></span>Do more with this image.</li>
</ul></div></div>
<div><div><div>cgen</div><div>ff_source=adobe-com&amp;ff_campaign=test</div></div></div>
<div><div><ul>
<li><picture><img src="http://localhost:2000/test/assets/media_.jpeg" alt=""></picture> Upscale<br><span class="icon icon-nba-upscale"></span><br>Prompt</li>
<li><picture><img src="http://localhost:2000/test/assets/media_.jpeg" alt=""></picture> Remove<br><span class="icon icon-nba-remove"></span><br>Prompt</li>
</ul></div></div>
</div>
</div>`;
25 changes: 25 additions & 0 deletions test/core/workflow/mocks/inline-action-three-columns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default `<div>
<div class="unity workflow-inline-action widget-inline-action">
<div>
<div>
<p><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload</p>
<p>Or tap here</p>
<p>Limits mobile</p>
<p>Legal</p>
</div>
<div>
<p><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload</p>
<p>Or tap here</p>
<p>Limits tablet</p>
<p>Legal</p>
</div>
<div>
<p><a href="/cc-shared/assets/svg/s2-icon-upload-20-n.svg">icon</a> Upload</p>
<p>Or drag and drop here</p>
<p>Limits desktop</p>
<p>Legal</p>
</div>
</div>
<div><div><ul><li><span class="icon icon-operation-removeBackground"></span></li></ul></div></div>
</div>
</div>`;
200 changes: 199 additions & 1 deletion test/core/workflow/workflow-acrobat/action-binder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@esm-bundle/chai';
import sinon from 'sinon';
import ActionBinder from '../../../../unitylibs/core/workflow/workflow-acrobat/action-binder.js';

let mockUpdateProgressBar = null;
const mockUpdateProgressBar = null;

describe('ActionBinder', () => {
let actionBinder;
Expand Down Expand Up @@ -1105,6 +1105,113 @@ describe('ActionBinder', () => {

expect(result).to.be.false;
});

it('should inject x_api_client_location in redirect URL for verb-dropzone config', async () => {
history.pushState({}, '', '/express/create/resume/cv');
actionBinder.workflowCfg.targetCfg = {
sendSplunkAnalytics: true,
experimentationOn: [],
showSplashScreen: false,
redirectParams: {
x_api_client_location: {
source: 'pageUrlPath',
stripPrefix: '/express/',
transform: 'reverseSegments',
},
},
};
actionBinder.redirectUrl = 'https://test-redirect-url.com?other=param';
const cOpts = { payload: {} };
const filesData = { test: 'data' };

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

expect(result).to.be.true;
expect(actionBinder.redirectUrl).to.include('x_api_client_location=cv_resume_create');
expect(actionBinder.redirectUrl).to.include('other=param');
history.pushState({}, '', '/');
});
});

describe('getComputedRedirectParams', () => {
let originalPath;

beforeEach(() => {
originalPath = window.location.pathname;
actionBinder.workflowCfg.targetCfg = {
sendSplunkAnalytics: true,
experimentationOn: [],
showSplashScreen: false,
redirectParams: {
x_api_client_location: {
source: 'pageUrlPath',
stripPrefix: '/express/',
transform: 'reverseSegments',
},
},
};
});

afterEach(() => {
history.pushState({}, '', originalPath);
});

it('should return queryString unchanged when redirectParams is not configured', () => {
actionBinder.workflowCfg.targetCfg = { sendSplunkAnalytics: true };
const queryString = 'x_api_client_location=old_value&other=param';
expect(actionBinder.getComputedRedirectParams(queryString)).to.equal(queryString);
});

it('should append x_api_client_location for /express/create/resume/cv', () => {
history.pushState({}, '', '/express/create/resume/cv');
expect(actionBinder.getComputedRedirectParams('other=param'))
.to.equal('other=param&x_api_client_location=cv_resume_create');
});

it('should append x_api_client_location for /express/create/resume', () => {
history.pushState({}, '', '/express/create/resume');
expect(actionBinder.getComputedRedirectParams('other=param'))
.to.equal('other=param&x_api_client_location=resume_create');
});

it('should update existing x_api_client_location in-place without changing other params', () => {
history.pushState({}, '', '/express/create/resume/cv');
const queryString = 'foo=bar&x_api_client_location=old_value&baz=qux';
expect(actionBinder.getComputedRedirectParams(queryString))
.to.equal('foo=bar&x_api_client_location=cv_resume_create&baz=qux');
});

it('should preserve other param encodings unchanged', () => {
history.pushState({}, '', '/express/create/resume/cv');
const queryString = 'foo=bar%20baz&other=val%2Bplus';
expect(actionBinder.getComputedRedirectParams(queryString))
.to.equal('foo=bar%20baz&other=val%2Bplus&x_api_client_location=cv_resume_create');
});

it('should not add x_api_client_location when URL path does not contain stripPrefix', () => {
history.pushState({}, '', '/some/other/path');
const queryString = 'other=param';
expect(actionBinder.getComputedRedirectParams(queryString)).to.equal(queryString);
});

it('should skip param when source is not pageUrlPath', () => {
actionBinder.workflowCfg.targetCfg.redirectParams.x_api_client_location.source = 'unknown';
history.pushState({}, '', '/express/create/resume/cv');
const queryString = 'other=param';
expect(actionBinder.getComputedRedirectParams(queryString)).to.equal(queryString);
});

it('should handle trailing slash in URL path', () => {
history.pushState({}, '', '/express/create/resume/cv/');
expect(actionBinder.getComputedRedirectParams(''))
.to.equal('x_api_client_location=cv_resume_create');
});

it('should return only the new param when queryString is empty', () => {
history.pushState({}, '', '/express/create/resume/cv');
expect(actionBinder.getComputedRedirectParams(''))
.to.equal('x_api_client_location=cv_resume_create');
});
});
});

Expand Down Expand Up @@ -2784,6 +2891,97 @@ describe('ActionBinder', () => {
});
});

describe('image-to-pdf verbs onboarding', () => {
const NEW_IMAGE_VERBS = [
'image-to-pdf',
'bmp-to-pdf',
'gif-to-pdf',
'tiff-to-pdf',
'indd-to-pdf',
'psd-to-pdf',
'ai-to-pdf',
];

NEW_IMAGE_VERBS.forEach((verb) => {
it(`should have correct limits configuration for ${verb} in LIMITS_MAP`, () => {
const verbLimits = ActionBinder.LIMITS_MAP[verb];
expect(verbLimits).to.exist;
expect(verbLimits).to.deep.equal([
'hybrid',
'allowed-filetypes-all',
'allowed-filetypes-heic',
'max-filesize-100-mb',
]);
});

it(`should configure ${verb} as hybrid mode`, () => {
expect(ActionBinder.LIMITS_MAP[verb][0]).to.equal('hybrid');
});

it(`should enable HEIC support for ${verb} via allowed-filetypes-heic`, () => {
expect(ActionBinder.LIMITS_MAP[verb]).to.include('allowed-filetypes-heic');
});

it(`should allow all file types for ${verb}`, () => {
expect(ActionBinder.LIMITS_MAP[verb]).to.include('allowed-filetypes-all');
});

it(`should have max-filesize-100-mb for ${verb}`, () => {
expect(ActionBinder.LIMITS_MAP[verb]).to.include('max-filesize-100-mb');
});
});
});

describe('HEIC validation for image-to-pdf verbs', () => {
beforeEach(() => {
actionBinder.limits = {
maxNumFiles: 100,
allowedFileTypes: ['application/pdf', 'image/jpeg', 'image/png', 'image/heic'],
maxFileSize: 104857600, // 100MB
};
actionBinder.workflowCfg.enabledFeatures = ['image-to-pdf'];
sinon.stub(actionBinder, 'dispatchErrorToast').resolves();
actionBinder.MULTI_FILE = false;
});

it('should accept a HEIC file with image/heic MIME type', async () => {
const files = [{ name: 'photo.heic', type: 'image/heic', size: 1048576 }];
const result = await actionBinder.validateFiles(files);
expect(result.isValid).to.be.true;
expect(result.validFiles).to.have.lengthOf(1);
});

it('should accept a HEIC file with empty MIME type via extension fallback', async () => {
const files = [{ name: 'photo.HEIC', type: '', size: 1048576 }];
const result = await actionBinder.validateFiles(files);
expect(result.isValid).to.be.true;
expect(result.validFiles).to.have.lengthOf(1);
});

it('should accept a regular supported image alongside HEIC support', async () => {
const files = [{ name: 'photo.jpg', type: 'image/jpeg', size: 1048576 }];
const result = await actionBinder.validateFiles(files);
expect(result.isValid).to.be.true;
expect(result.validFiles).to.have.lengthOf(1);
});

it('should still reject an unsupported file type for image-to-pdf', async () => {
const files = [{ name: 'archive.zip', type: 'application/zip', size: 1048576 }];
const result = await actionBinder.validateFiles(files);
expect(result.isValid).to.be.false;
expect(result.validFiles).to.be.empty;
expect(actionBinder.dispatchErrorToast.calledWith('validation_error_unsupported_type')).to.be.true;
});

it('should not apply the HEIC extension fallback when the verb does not support HEIC', async () => {
actionBinder.limits.allowedFileTypes = ['application/pdf', 'image/jpeg', 'image/png'];
const files = [{ name: 'photo.heic', type: '', size: 1048576 }];
const result = await actionBinder.validateFiles(files);
expect(result.isValid).to.be.false;
expect(actionBinder.dispatchErrorToast.calledWith('validation_error_unsupported_type')).to.be.true;
});
});

describe('filterFilesWithPdflite', () => {
beforeEach(() => {
actionBinder.MULTI_FILE = false;
Expand Down
37 changes: 37 additions & 0 deletions test/core/workflow/workflow-acrobat/upload-handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,43 @@ describe('UploadHandler', () => {
});
});

describe('getEffectiveFileType', () => {
it('should tag an empty-MIME .heic file as image/heic when the verb supports HEIC', async () => {
mockActionBinder.limits = { allowedFileTypes: ['application/pdf', 'image/heic'] };
const file = new File(['content'], 'photo.heic', { type: '' });
const result = await uploadHandler.getEffectiveFileType(file);
expect(result).to.equal('image/heic');
});

it('should be case-insensitive for the .HEIC extension', async () => {
mockActionBinder.limits = { allowedFileTypes: ['application/pdf', 'image/heic'] };
const file = new File(['content'], 'photo.HEIC', { type: '' });
const result = await uploadHandler.getEffectiveFileType(file);
expect(result).to.equal('image/heic');
});

it('should preserve the reported MIME type for a .heic file that already has one', async () => {
mockActionBinder.limits = { allowedFileTypes: ['application/pdf', 'image/heic'] };
const file = new File(['content'], 'photo.heic', { type: 'image/heic' });
const result = await uploadHandler.getEffectiveFileType(file);
expect(result).to.equal('image/heic');
});

it('should not tag a .heic file when the verb does not support HEIC', async () => {
mockActionBinder.limits = { allowedFileTypes: ['application/pdf', 'image/jpeg'] };
const file = new File(['content'], 'photo.heic', { type: '' });
const result = await uploadHandler.getEffectiveFileType(file);
expect(result).to.equal('');
});

it('should return the reported MIME type for a non-HEIC file', async () => {
mockActionBinder.limits = { allowedFileTypes: ['application/pdf', 'image/heic'] };
const file = new File(['content'], 'photo.jpg', { type: 'image/jpeg' });
const result = await uploadHandler.getEffectiveFileType(file);
expect(result).to.equal('image/jpeg');
});
});

describe('PDF Chunking', () => {
it('should chunk and upload PDF successfully', async () => {
const assetDataArray = [{
Expand Down
Loading
Loading