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
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
45 changes: 20 additions & 25 deletions test/utils/experiment-provider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,22 @@ describe('getExperimentData', () => {
expect(result).to.deep.equal(mockTargetData);
});

it('should reject when target returns empty decisions', async () => {
await testErrorScenario(
'Target proposition returned but no valid data for scopes: acom_unity_acrobat_add-comment_us',
() => setupMock(createMockResult(null, [])),
);
it('should resolve with null when target returns empty decisions', async () => {
setupMock(createMockResult(null, []));
const result = await getExperimentData(['acom_unity_acrobat_add-comment_us']);
expect(result).to.equal(null);
});

it('should reject when target returns empty items', async () => {
await testErrorScenario(
'Target proposition returned but no valid data for scopes: acom_unity_acrobat_add-comment_us',
() => setupMock({ decisions: [{ items: [] }] }),
);
it('should resolve with null when target returns empty items', async () => {
setupMock({ decisions: [{ items: [] }] });
const result = await getExperimentData(['acom_unity_acrobat_add-comment_us']);
expect(result).to.equal(null);
});

it('should reject when target returns invalid structure', async () => {
await testErrorScenario(
'Target proposition returned but no valid data for scopes: acom_unity_acrobat_add-comment_us',
() => setupMock({ invalid: 'structure' }),
);
it('should resolve with null when target returns invalid structure', async () => {
setupMock({ invalid: 'structure' });
const result = await getExperimentData(['acom_unity_acrobat_add-comment_us']);
expect(result).to.equal(null);
});

it('should reject when satellite track throws exception', async () => {
Expand All @@ -108,18 +105,16 @@ describe('getExperimentData', () => {
);
});

it('should reject when target result is null', async () => {
await testErrorScenario(
'Target proposition returned but no valid data for scopes: acom_unity_acrobat_add-comment_us',
() => setupMock(null),
);
it('should resolve with null when target result is null', async () => {
setupMock(null);
const result = await getExperimentData(['acom_unity_acrobat_add-comment_us']);
expect(result).to.equal(null);
});

it('should reject when target result is undefined', async () => {
await testErrorScenario(
'Target proposition returned but no valid data for scopes: acom_unity_acrobat_add-comment_us',
() => setupMock(undefined),
);
it('should resolve with null when target result is undefined', async () => {
setupMock(undefined);
const result = await getExperimentData(['acom_unity_acrobat_add-comment_us']);
expect(result).to.equal(null);
});
});

Expand Down
21 changes: 21 additions & 0 deletions unitylibs/core/styles/splash-screen.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ body > .splash-loader {
display: flex;
}

.splash-loader.dark .text .con-button {
border-color: white !important;
color: white !important;
}

.splash-loader.dark .text .con-button:hover {
color: black !important;
}

.progress-holder {
width: 100%;
}
Expand Down Expand Up @@ -129,6 +138,10 @@ body > .splash-loader {
font-weight: 700;
}

.splash-loader.dark .progress-holder .spectrum-ProgressBar--sideLabel .spectrum-ProgressBar-percentage {
color: white !important;
}

.splash-loader .text .icon-area img,
.splash-loader .text video {
width: 157px;
Expand All @@ -148,6 +161,10 @@ body > .splash-loader {
margin: 0 0 var(--spacing-l) 0;
}

.splash-loader.dark .text-block [class^="heading"]:first-of-type {
color: white !important;
}

.splash-loader .text-block p.icon-area {
margin-block: var(--spacing-s);
}
Expand All @@ -167,6 +184,10 @@ body > .splash-loader {
font-weight: 700;
}

.splash-loader.dark .text-block p {
color: white !important;
}

@media screen and (min-width: 600px) {
.splash-loader h1,
.splash-loader h2,
Expand Down
Loading
Loading