diff --git a/README.md b/README.md index 61dc00699..eb7685345 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ Spec files live alongside the code they are testing. For example, in peer-review ## UI-Router and States See any *.routes.js file as an example. +**Important:** Make sure the url in your routes files ends with a slash `/` + ## Contributing ### Style Guide and Naming Conventions @@ -78,9 +80,9 @@ Jade Files ``` .wrapper h1 Lorem ipsum - + p Sibling tag - + .wrapper2 p Child ``` @@ -99,7 +101,7 @@ SCSS Files height: 100px; width: 100px; } - + .inside-box { font-size: 14px; @media screen and (min-width: 768px) { diff --git a/app/community/members.controller.js b/app/community/members.controller.js index f8ce7ab45..6ed34b468 100644 --- a/app/community/members.controller.js +++ b/app/community/members.controller.js @@ -10,7 +10,7 @@ ctrl.notSearch = true; ctrl.showing = 'list'; ctrl.domain = CONSTANTS.domain; - ctrl.currentMonth = 'November 2015'; + ctrl.currentMonth = 'December 2015'; ctrl.memberLeaderboard = []; ctrl.copilots = []; CommunityDataService.getMembersData() diff --git a/app/directives/badges/badge-tooltip.spec.js b/app/directives/badges/badge-tooltip.spec.js index 17ff12223..43ec2ba27 100644 --- a/app/directives/badges/badge-tooltip.spec.js +++ b/app/directives/badges/badge-tooltip.spec.js @@ -6,14 +6,14 @@ describe('Badge Tooltip Directive', function() { var spotlightChallenge = mockData.getMockSpotlightChallenges()[0]; beforeEach(function() { - bard.appModule('tcUIComponents'); + bard.appModule('topcoder'); bard.inject(this, '$compile', '$rootScope'); scope = $rootScope.$new(); }); bard.verifyNoOutstandingHttpRequests(); - xdescribe('Badge Tooltip', function() { + describe('Badge Tooltip', function() { var tooltip; beforeEach(function() { @@ -89,5 +89,18 @@ describe('Badge Tooltip Directive', function() { expect(dataDiv).not.to.null; expect(dataDiv.hasClass('ng-hide')).to.equal(true); }); + + it('should trigger mouseenter handler ', function() { + tooltip.trigger('mouseenter'); + var tooltipElement = tooltip.children(0); + expect(tooltipElement.css('z-index')).to.equal('2000'); + expect(tooltip.isolateScope().hide).to.equal(false); + }); + + it('should trigger mouseleave handler ', function() { + tooltip.trigger('mouseleave'); + tooltipElement = tooltip.children(0); + expect(tooltip.isolateScope().hide).to.equal(true); + }); }); }); diff --git a/app/directives/challenge-tile/challenge-tile.directive.jade b/app/directives/challenge-tile/challenge-tile.directive.jade index 3ec2e022d..325bd5f76 100644 --- a/app/directives/challenge-tile/challenge-tile.directive.jade +++ b/app/directives/challenge-tile/challenge-tile.directive.jade @@ -2,80 +2,75 @@ .active-challenge(ng-show="challenge.status === 'ACTIVE'") header - .top - a.name(ng-href="{{challenge|challengeLinks:'detail'}}") #[span {{challenge.name}}] + a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") #[span {{challenge.name}}] - .challenge-track - - p.subtrack-color {{challenge.subTrack | underscoreStrip}} + p.subtrack-color {{challenge.subTrack | underscoreStrip}} challenge-links(challenge="challenge", view="'tile'") + .challenge-card__bottom.challenge-card__bottom--active + .challenge-details + p.currentPhase {{challenge.userCurrentPhase}} - .challenge-details - p.currentPhase {{challenge.userCurrentPhase}} - - .challenge-calendar(ng-show="challenge.userCurrentPhaseEndTime") - p.ends-in Ends In - p.time-remaining {{challenge.userCurrentPhaseEndTime[0]}} - p.unit-of-time {{challenge.userCurrentPhaseEndTime[1]}} + .challenge-calendar(ng-show="challenge.userCurrentPhaseEndTime") + p.ends-in Ends In + p.time-remaining {{challenge.userCurrentPhaseEndTime[0]}} + p.unit-of-time {{challenge.userCurrentPhaseEndTime[1]}} - .stalled-challenge(ng-hide="challenge.userCurrentPhaseEndTime") This challenge is currently paused. + .stalled-challenge(ng-hide="challenge.userCurrentPhaseEndTime") This challenge is currently paused. - .phase-action(ng-switch="challenge.userAction") - a.tc-btn.tc-btn-s.tc-btn-wide.tc-btn-ghost.submit(ng-switch-when="Submit", ng-href="{{challenge|challengeLinks:'detail'}}") Submit + .phase-action(ng-show="challenge.userAction", ng-switch="challenge.userAction") + a.tc-btn.tc-btn-s.tc-btn-wide.tc-btn-ghost.submit(ng-switch-when="Submit", ng-href="{{challenge|challengeLinks:'detail'}}") Submit - .submitted(ng-switch-when="Submitted") Submitted + .submitted(ng-switch-when="Submitted") Submitted - // TODO: Need styling and JS logic for this one - .registered(ng-switch-when="Registered") Registered + // TODO: Need styling and JS logic for this one + .registered(ng-switch-when="Registered") Registered - // Only show if not data science track - p.roles - span(ng-hide="challenge.track === 'DATA_SCIENCE'") - span Role: - span {{challenge.userDetails.roles | listRoles}} + // Only show if not data science track + p.roles + span(ng-hide="challenge.track === 'DATA_SCIENCE'") + span Role: + span {{challenge.userDetails.roles | listRoles}} .completed-challenge( ng-show="challenge.status === 'COMPLETED' || challenge.status === 'PAST'", ng-switch="challenge.track") - .challenge-track header - .top - a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}} + a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}} - p.subtrack-color {{challenge.subTrack | underscoreStrip}} + p.subtrack-color {{challenge.subTrack | underscoreStrip}} p.date-completed {{challenge.submissionEndDate | date : 'MMMM yyyy'}} .winner-ribbon(ng-show="challenge.wonFirst") - .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track") + .challenge-card__bottom.challenge-card__bottom--completed + .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track") - div - .marathon-score - p.score {{challenge.pointTotal || 0 }} + div + .marathon-score + p.score {{challenge.pointTotal || 0 }} - p Total Points + p Total Points - .challenge-details(ng-switch-when="DEVELOP") - dev-challenge-user-place(challenge="challenge", view="view") + .challenge-details(ng-switch-when="DEVELOP") + dev-challenge-user-place(challenge="challenge", view="view") - .challenge-details(ng-switch-when="DESIGN") - design-challenge-user-place(challenge="challenge", view="view") + .challenge-details(ng-switch-when="DESIGN") + design-challenge-user-place(challenge="challenge", view="view") - // Only show if not data science track - p.roles - span(ng-hide="challenge.track === 'DATA_SCIENCE'") - span Role: - span {{challenge.userDetails.roles | listRoles}} + // Only show if not data science track + p.roles + span(ng-hide="challenge.track === 'DATA_SCIENCE'") + span Role: + span {{challenge.userDetails.roles | listRoles}} .challenge.list-view(ng-show="view=='list'", ng-class="challenge.track") .active-challenge(ng-show="challenge.status === 'ACTIVE'") - .challenge-track header - a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}} + a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}} p.subtrack-color {{challenge.subTrack | underscoreStrip}} @@ -102,10 +97,9 @@ .completed-challenge( ng-show="challenge.status === 'COMPLETED' || challenge.status === 'PAST'", ng-switch="challenge.track") - .challenge-track header - a.name(ng-href="{{challenge|challengeLinks:'detail'}}") {{challenge.name}} + a.name(ng-href="{{challenge|challengeLinks:'detail'}}", title="{{challenge.name}}") {{challenge.name}} p.subtrack-color {{challenge.subTrack | underscoreStrip}} @@ -113,11 +107,10 @@ .challenge-details(ng-switch-when="DATA_SCIENCE", ng-switch="challenge.subTrack", ng-class="challenge.track") - div - .marathon-score - p.score {{challenge.pointTotal || 0 }} + .marathon-score + p.score {{challenge.pointTotal || 0 }} - p Total Points + p Total Points .challenge-details(ng-switch-when="DEVELOP") dev-challenge-user-place(challenge="challenge", view="view") diff --git a/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade b/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade index 99156ddc9..eeca35389 100644 --- a/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade +++ b/app/directives/challenge-user-place/dev-challenge-user-place.directive.jade @@ -4,7 +4,7 @@ p.place(ng-show="challenge.userStatus === 'PASSED_SCREENING'") Passed Screening p.place(ng-show="challenge.userStatus === 'COMPLETED'") COMPLETED - .challenge-score(ng-class="{hidden: challenge.userStatus !== 'PASSED_REVIEW'}") + .challenge-score(ng-hide="challenge.userStatus !== 'PASSED_REVIEW'") p.score {{challenge.userDetails.submissionReviewScore/100 | percentage}} p Review Score diff --git a/app/directives/page-state-header/page-state-header.directive.js b/app/directives/page-state-header/page-state-header.directive.js index adc3de015..37ba60297 100644 --- a/app/directives/page-state-header/page-state-header.directive.js +++ b/app/directives/page-state-header/page-state-header.directive.js @@ -4,7 +4,7 @@ angular.module('tcUIComponents').directive('pageStateHeader', function() { return { restrict: 'E', - templateUrl: 'directives/page-state-header/page-state-header.directive.html', + templateUrl: 'directives/page-state-header/page-state-header.html', transclude: true, scope: { handle: '@', diff --git a/app/directives/page-state-header/page-state-header.directive.jade b/app/directives/page-state-header/page-state-header.jade similarity index 96% rename from app/directives/page-state-header/page-state-header.directive.jade rename to app/directives/page-state-header/page-state-header.jade index ba80d2b90..6ed6a0e3c 100644 --- a/app/directives/page-state-header/page-state-header.directive.jade +++ b/app/directives/page-state-header/page-state-header.jade @@ -1,7 +1,7 @@ .page-state-header header .page-info - h1 {{pageTitle}} + h1 {{pageTitle | track}} div(ng-transclude) diff --git a/app/directives/tc-file-input/tc-file-input.directive.js b/app/directives/tc-file-input/tc-file-input.directive.js index a7cb39c21..bc4a18554 100644 --- a/app/directives/tc-file-input/tc-file-input.directive.js +++ b/app/directives/tc-file-input/tc-file-input.directive.js @@ -28,8 +28,8 @@ var fileInput = $(element[0]).find('.none'); var fileNameInput = $(element[0]).find('input[type=text]'); - fileInput.bind('change', function() { - var file = fileInput[0].files[0]; + fileInput.bind('change', function(event) { + var file = event.target.files[0]; // About 1 in 20 times, the file is undefined (must be race condition) // Return early in this case so no errors are thrown diff --git a/app/directives/tc-file-input/tc-file-input.jade b/app/directives/tc-file-input/tc-file-input.jade index c1b097480..f67de11ff 100644 --- a/app/directives/tc-file-input/tc-file-input.jade +++ b/app/directives/tc-file-input/tc-file-input.jade @@ -2,10 +2,11 @@ label.tc-label {{labelText}} span.lowercase(ng-if="showFileType") {{ ' *(.' + fileType + ')'}} - span.tc-label__mandatory.lowercase(ng-if="mandatory") #[span *]mandatory - .tc-file-field__inputs - input.tc-file-field__input(type="text", placeholder="{{placeholder}}", disabled) + .tc-label__wrapper + input.tc-file-field__input(type="text", placeholder="{{placeholder}}", disabled) + + span.tc-label__asterisk.lowercase(ng-if="mandatory") #[span *]mandatory button.tc-btn(ng-click="selectFile()") {{buttonText}} diff --git a/app/directives/tc-file-input/tc-file-input.spec.js b/app/directives/tc-file-input/tc-file-input.spec.js index 3f2b398aa..ef1917cc8 100644 --- a/app/directives/tc-file-input/tc-file-input.spec.js +++ b/app/directives/tc-file-input/tc-file-input.spec.js @@ -1,19 +1,148 @@ /* jshint -W117, -W030 */ describe('Topcoder File Input Directive', function() { - var scope; - - // USE AS TEMPLATE FOR DIRECTIVES + var scope, element, isolateScope, fileInput; beforeEach(function() { - bard.appModule('tcUIComponents'); + bard.appModule('topcoder'); bard.inject(this, '$compile', '$rootScope'); + scope = $rootScope.$new(); + + var html = '' + + '<form>' + + '<tc-file-input ' + + 'label-text="Preview Image"' + + 'field-id="DESIGN_COVER"' + + 'button-text="Add File"' + + 'file-type="jpg,jpeg,png"' + + 'placeholder="Image file as .jpg or .png"' + + 'mandatory="true"' + + 'set-file-reference="vm.setFileReference(file, fieldId)"' + + 'ng-model="vm.submissionForm.submissionZip"' + + ' />' + + '</form>'; + var form = angular.element(html); + element = form.find('tc-file-input'); + var formElement = $compile(form)(scope); + scope.$digest(); + + isolateScope = element.isolateScope(); + }); + + beforeEach(function() { + fileInput = $(element).find('.none')[0]; + }); + + afterEach(function() { + scope.$destroy(); + fileInput = undefined; }); bard.verifyNoOutstandingHttpRequests(); - xdescribe('', function() { - beforeEach(function() {}); + describe('selectFile', function() { + it('triggers a click on the file input', function() { + var mockClick = sinon.spy(fileInput, 'click'); + + isolateScope.selectFile(); + scope.$digest(); + + expect(mockClick).calledOnce; + }); + }); + + describe('a change event on the file input', function() { + var fileNameInput, fileList, mockSetFileReference; + + beforeEach(function() { + fileNameInput = $(element).find('input[type=text]')[0]; + fileList = { + 0: { + name: 'test.png', + size: 50, + type: 'image/png' + }, + length: 1, + item: function (index) { return file; } + }; + + mockSetFileReference = sinon.spy(isolateScope, 'setFileReference'); + }); + + afterEach(function() { + fileNameInput = undefined; + fileList = undefined; + mockSetFileReference = undefined; + }); + + it('sets the value of the fileNameInput with the name of the file', function() { + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }); + + expect(fileNameInput.value).to.equal('test.png'); + }); + + describe('with a valid file', function() { + beforeEach(function() { + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }); + }); + + it('calls setFileReference', function() { + expect(mockSetFileReference).calledOnce; + }); + + it('has ng-valid-filesize class', function() { + expect($(fileInput).hasClass('ng-valid-filesize')).to.be.true; + }); + + it('has ng-valid-required class', function() { + expect($(fileInput).hasClass('ng-valid-required')).to.be.true; + }); + }); + + describe('with a file that\'s greater than 500MB', function() { + beforeEach(function() { + fileList[0].size = 500000001; + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }); + }); + + it('does not call setFileReference', function() { + expect(mockSetFileReference).not.calledOnce; + }); + + it('has ng-touched and ng-invalid-filesize classes', function() { + expect($(fileInput).hasClass('ng-invalid-filesize')).to.be.true; + expect($(fileInput).hasClass('ng-touched')).to.be.true; + }); + }); + + describe('with a file type that\'s not in the list of fileTypes given to the directive', function() { + beforeEach(function() { + fileList[0].type = 'application/zip'; + + $(fileInput).triggerHandler({ + type: 'change', + target: { files: fileList } + }); + }); + + it('does not call setFileReference', function() { + expect(mockSetFileReference).not.calledOnce; + }); + + it('has ng-touched and ng-invalid-required classes', function() { + expect($(fileInput).hasClass('ng-invalid-required')).to.be.true; + expect($(fileInput).hasClass('ng-touched')).to.be.true; + }); + }); - it('', function() {}); }); }); diff --git a/app/directives/tc-form-fonts/tc-form-fonts.directive.js b/app/directives/tc-form-fonts/tc-form-fonts.directive.js index 2b08d47fd..9b097a24e 100644 --- a/app/directives/tc-form-fonts/tc-form-fonts.directive.js +++ b/app/directives/tc-form-fonts/tc-form-fonts.directive.js @@ -42,6 +42,9 @@ isFontSourceRequired: false }; + // Initialize font form data + $scope.formFonts = { 0: _.assign({id: 0}, angular.copy(emptyFont)) }; + $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/); $scope.selectFont = function(newFont) { diff --git a/app/directives/tc-form-fonts/tc-form-fonts.spec.js b/app/directives/tc-form-fonts/tc-form-fonts.spec.js index 5c17c2df7..cfe6f16e6 100644 --- a/app/directives/tc-form-fonts/tc-form-fonts.spec.js +++ b/app/directives/tc-form-fonts/tc-form-fonts.spec.js @@ -1,19 +1,233 @@ /* jshint -W117, -W030 */ describe('Topcoder Form Fonts Directive', function() { - var scope; - - // USE AS TEMPLATE FOR DIRECTIVES + var scope, element, isolateScope; beforeEach(function() { - bard.appModule('tcUIComponents'); + bard.appModule('topcoder'); bard.inject(this, '$compile', '$rootScope'); + scope = $rootScope.$new(); + scope.formFonts = []; + + var form = angular.element('<form><tc-form-fonts form-fonts="formFonts" /></form>'); + element = form.find('tc-form-fonts'); + var formElement = $compile(form)(scope); + scope.$digest(); + + isolateScope = element.isolateScope(); + }); + + afterEach(function() { + scope.$destroy(); }); bard.verifyNoOutstandingHttpRequests(); - xdescribe('', function() { - beforeEach(function() {}); + describe('is initialized with', function() { + it('empty font data', function() { + var defaultFormFont = isolateScope.formFonts[0]; + + expect(defaultFormFont.id).to.equal(0); + expect(defaultFormFont.source).to.equal(''); + expect(defaultFormFont.name).to.equal(''); + expect(defaultFormFont.sourceUrl).to.equal(''); + expect(defaultFormFont.isFontUrlRequired).to.be.false; + expect(defaultFormFont.isFontUrlDisabled).to.be.true; + expect(defaultFormFont.isFontNameRequired).to.be.false; + expect(defaultFormFont.isFontNameDisabled).to.be.true; + expect(defaultFormFont.isFontSourceRequired).to.be.false; + }); + + it('a font list', function() { + var fontList = isolateScope.fontList0; + + expect(fontList).to.be.an.array; + expect(fontList).to.have.length.of.at.least(1); + }); + + it('a regular expression', function() { + expect(isolateScope.urlRegEx).to.be.an.instanceof(RegExp); + }); + }); + + describe('selectFont', function() { + var newFont, targetedFont; + + beforeEach(function() { + newFont = { + id: 0, + value: 'FONTS_DOT_COM' + }; + + targetedFont = isolateScope.formFonts[0]; + }); + + afterEach(function() { + newFont = undefined; + targetedFont = undefined; + }); + + it('updates the targeted font source with the new value', function() { + expect(targetedFont.source).to.equal(''); + + isolateScope.selectFont(newFont); + scope.$digest(); + + expect(targetedFont.source).to.equal('FONTS_DOT_COM'); + }); + + it('sets disabled properties to false', function() { + expect(targetedFont.isFontNameDisabled).to.be.true; + expect(targetedFont.isFontUrlDisabled).to.be.true; + + isolateScope.selectFont(newFont); + scope.$digest(); + + expect(targetedFont.isFontNameDisabled).to.be.false; + expect(targetedFont.isFontUrlDisabled).to.be.false; + }); + + it('sets required properties to true', function() { + expect(targetedFont.isFontNameRequired).to.be.false; + expect(targetedFont.isFontUrlRequired).to.be.false; + + isolateScope.selectFont(newFont); + scope.$digest(); + + expect(targetedFont.isFontNameRequired).to.be.true; + expect(targetedFont.isFontUrlRequired).to.be.true; + }); + + it('sets isFontNameRequired to true and isFontUrlRequired to false when STUDIO_STANDARD_FONTS_LIST is selected', function() { + expect(targetedFont.isFontNameRequired).to.be.false; + expect(targetedFont.isFontUrlRequired).to.be.false; + + isolateScope.selectFont({id: 0, value: 'STUDIO_STANDARD_FONTS_LIST'}); + scope.$digest(); + + expect(targetedFont.isFontNameRequired).to.be.true; + expect(targetedFont.isFontUrlRequired).to.be.false; + }); + }); + + describe('createAdditionalFontFieldset', function() { + it('creates a new fieldset', function() { + expect(Object.keys(isolateScope.formFonts).length).to.equal(1); + + isolateScope.createAdditionalFontFieldset(); + scope.$digest(); + + expect(Object.keys(isolateScope.formFonts).length).to.equal(2); + }); + + it('adds an incremented id to the new fieldset', function() { + var id = isolateScope.formFonts[0].id; + + isolateScope.createAdditionalFontFieldset(); + scope.$digest(); + + expect(isolateScope.formFonts[1]).to.exist; + expect(isolateScope.formFonts[1].id).to.equal(id + 1); + }); + }); + + describe('deleteFontFieldset', function() { + it('deletes the selected font fieldset', function() { + isolateScope.createAdditionalFontFieldset(); + scope.$digest(); + + expect(Object.keys(isolateScope.formFonts).length).to.equal(2); + + isolateScope.deleteFontFieldset(1); + scope.$digest(); + + expect(Object.keys(isolateScope.formFonts).length).to.equal(1); + }); + + it('resets the font fieldset when it\'s the only one', function() { + var font = isolateScope.formFonts[0]; + + expect(font.source).to.equal(''); + + font.source = 'dropdown selection'; + scope.$digest(); + + expect(font.source).to.equal('dropdown selection'); + + isolateScope.deleteFontFieldset(0); + scope.$digest(); + + expect(isolateScope.formFonts[0].source).to.equal(''); + }); + }); + + describe('isButtonDisabled', function() { + var button; + + beforeEach(function() { + button = $(element).find('.fieldset__button')[0]; + }); + + afterEach(function() { + button = undefined; + }); + + it('disables the button when no fields are filled out', function() { + expect(button.disabled).to.be.true; + }); + + it('disables the button when 1 field is filled out', function() { + isolateScope.formFonts[0].source = 'FONTS_DOT_COM'; + scope.$digest(); + + expect(button.disabled).to.be.true; + }); + + it('disables the button when 2 fields are filled out', function() { + isolateScope.formFonts[0].source = 'FONTS_DOT_COM'; + isolateScope.formFonts[0].sourceUrl = 'url.com'; + scope.$digest(); + + expect(button.disabled).to.be.true; + }); + + it('enables the button when all fields are filled out', function() { + isolateScope.formFonts[0].source = 'FONTS_DOT_COM'; + isolateScope.formFonts[0].name = 'name'; + isolateScope.formFonts[0].sourceUrl = 'url.com'; + scope.$digest(); + + expect(button.disabled).to.be.false; + }); + + it('disables the button when any field in any fieldset is empty', function() { + expect(button.disabled).to.be.true; + + // Fill out first fieldset + isolateScope.formFonts[0].source = 'FONTS_DOT_COM'; + isolateScope.formFonts[0].name = 'name'; + isolateScope.formFonts[0].sourceUrl = 'url.com'; + scope.$digest(); + + expect(button.disabled).to.be.false; + + isolateScope.createAdditionalFontFieldset(); + scope.$digest(); + + expect(button.disabled).to.be.true; + + // Fill out second fieldset + isolateScope.formFonts[1].source = 'FONTS_DOT_COM2'; + isolateScope.formFonts[1].name = 'name'; + isolateScope.formFonts[1].sourceUrl = 'url2.com'; + scope.$digest(); + + expect(button.disabled).to.be.false; + + // Empty a field in the first fieldset + isolateScope.formFonts[0].name = ''; + scope.$digest(); - it('', function() {}); + expect(button.disabled).to.be.true; + }); }); }); diff --git a/app/directives/tc-form-stockart/tc-form-stockart.directive.js b/app/directives/tc-form-stockart/tc-form-stockart.directive.js index c5c3fca12..c2ae46bdb 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.directive.js +++ b/app/directives/tc-form-stockart/tc-form-stockart.directive.js @@ -26,6 +26,9 @@ isFileNumberRequired: false }; + // Initialize stockart form data + $scope.formStockarts = { 0: _.assign({id: 0}, angular.copy(emptyStockart)) }; + $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/); $scope.createAdditionalStockartFieldset = function() { diff --git a/app/directives/tc-form-stockart/tc-form-stockart.jade b/app/directives/tc-form-stockart/tc-form-stockart.jade index 3955c0eb3..de63abc07 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.jade +++ b/app/directives/tc-form-stockart/tc-form-stockart.jade @@ -4,6 +4,7 @@ tc-input.fieldset__input( label-text="Photo Description", asterisk-text="Field can't be empty", + show-asterisk-text="true", placeholder="A picture of a girl", input-value="stockart.description", input-name="photoDescription{{stockartId}}", @@ -15,6 +16,7 @@ tc-input.fieldset__input( label-text="Photo URL", asterisk-text="Field can't be empty", + show-asterisk-text="true", placeholder="www.istockphoto.com", input-value="stockart.sourceUrl", input-name="photoURL{{stockartId}}", @@ -30,6 +32,7 @@ tc-input.fieldset__input( label-text="File Number", asterisk-text="Field can't be empty", + show-asterisk-text="true", placeholder="u2434312", input-value="stockart.fileNumber", input-name="fileNumber{{stockartId}}", diff --git a/app/directives/tc-form-stockart/tc-form-stockart.spec.js b/app/directives/tc-form-stockart/tc-form-stockart.spec.js index fe821738a..f8dfeef5d 100644 --- a/app/directives/tc-form-stockart/tc-form-stockart.spec.js +++ b/app/directives/tc-form-stockart/tc-form-stockart.spec.js @@ -1,19 +1,227 @@ /* jshint -W117, -W030 */ describe('Topcoder Form Stockart Directive', function() { - var scope; - - // USE AS TEMPLATE FOR DIRECTIVES + var scope, element, isolateScope; beforeEach(function() { - bard.appModule('tcUIComponents'); + bard.appModule('topcoder'); bard.inject(this, '$compile', '$rootScope'); + scope = $rootScope.$new(); + scope.stockarts = []; + + var form = angular.element('<form><tc-form-stockart form-stockarts="stockarts" /></form>'); + element = form.find('tc-form-stockart'); + var formElement = $compile(form)(scope); + scope.$digest(); + + isolateScope = element.isolateScope(); + }); + + afterEach(function() { + scope.$destroy(); }); bard.verifyNoOutstandingHttpRequests(); - xdescribe('', function() { - beforeEach(function() {}); + describe('is initialized with', function() { + it('empty stockart data', function() { + var initialStockart = isolateScope.formStockarts[0]; + + expect(initialStockart.id).to.equal(0); + expect(initialStockart.description).to.equal(''); + expect(initialStockart.sourceUrl).to.equal(''); + expect(initialStockart.fileNumber).to.equal(''); + expect(initialStockart.isPhotoDescriptionRequired).to.equal(false); + expect(initialStockart.isPhotoURLRequired).to.equal(false); + expect(initialStockart.isFileNumberRequired).to.equal(false); + }); + + it('a regular expression', function() { + expect(isolateScope.urlRegEx).to.be.an.instanceof(RegExp); + }); + }); + + describe('createAdditionalStockartFieldset', function() { + it('creates a new fieldset', function() { + expect(Object.keys(isolateScope.formStockarts).length).to.equal(1); + + isolateScope.createAdditionalStockartFieldset(); + scope.$digest(); + + expect(Object.keys(isolateScope.formStockarts).length).to.equal(2); + }); + + it('adds an incremented id to the new fieldset', function() { + var id = isolateScope.formStockarts[0].id; + + isolateScope.createAdditionalStockartFieldset(); + scope.$digest(); + + expect(isolateScope.formStockarts[1]).to.exist; + expect(isolateScope.formStockarts[1].id).to.equal(id + 1); + }); + }) + + describe('deleteStockartFieldset', function() { + it('deletes the selected stockart fieldset', function() { + isolateScope.createAdditionalStockartFieldset(); + scope.$digest(); + + expect(Object.keys(isolateScope.formStockarts).length).to.equal(2); + + isolateScope.deleteStockartFieldset(1); + scope.$digest(); + + expect(Object.keys(isolateScope.formStockarts).length).to.equal(1); + }); + + it('resets the stockart fieldset when it\'s the only one', function() { + var stockart = isolateScope.formStockarts[0]; + + expect(stockart.description).to.equal(''); + + stockart.description = 'a funny cat picture'; + scope.$digest(); + + expect(stockart.description).to.equal('a funny cat picture'); + + isolateScope.deleteStockartFieldset(0); + scope.$digest(); + + expect(isolateScope.formStockarts[0].description).to.equal(''); + }); + }); + + describe('isButtonDisabled', function() { + var button; + + beforeEach(function() { + button = $(element).find('.fieldset__button')[0]; + }); + + afterEach(function() { + button = undefined; + }); + + it('disables the button when no fields are filled out', function() { + expect(button.disabled).to.be.true; + }); + + it('disables the button when 1 field is filled out', function() { + isolateScope.formStockarts[0].description = 'test description'; + scope.$digest(); + + expect(button.disabled).to.be.true; + }); + + it('disables the button when 2 fields are filled out', function() { + isolateScope.formStockarts[0].description = 'test description'; + isolateScope.formStockarts[0].sourceUrl = 'url'; + scope.$digest(); + + expect(button.disabled).to.be.true; + }); + + it('enables the button when all fields are filled out', function() { + isolateScope.formStockarts[0].description = 'test description'; + isolateScope.formStockarts[0].sourceUrl = 'url'; + isolateScope.formStockarts[0].fileNumber = '123'; + scope.$digest(); + + expect(button.disabled).to.be.false; + }); + + it('disables the button when any field in any fieldset is empty', function() { + expect(button.disabled).to.be.true; + + // Fill out first fieldset + isolateScope.formStockarts[0].description = 'test description'; + isolateScope.formStockarts[0].sourceUrl = 'url.com'; + isolateScope.formStockarts[0].fileNumber = '123'; + scope.$digest(); + + expect(button.disabled).to.be.false; + + isolateScope.createAdditionalStockartFieldset(); + scope.$digest(); + + expect(button.disabled).to.be.true; + + // Fill out second fieldset + isolateScope.formStockarts[1].description = 'test description2'; + isolateScope.formStockarts[1].sourceUrl = 'url2.com'; + isolateScope.formStockarts[1].fileNumber = '1232'; + scope.$digest(); + + expect(button.disabled).to.be.false; + + // Empty a field in the first fieldset + isolateScope.formStockarts[0].fileNumber = ''; + scope.$digest(); + + expect(button.disabled).to.be.true; + }); + }); + + describe('showMandatoryMessage', function() { + describe('sets the stockart required properties to false when all fields are', function() { + var stockart; + + beforeEach(function() { + stockart = isolateScope.formStockarts[0]; + stockart.description = 'test description'; + stockart.sourceUrl = 'url.com'; + stockart.fileNumber = '123'; + scope.$digest(); + }); + + afterEach(function() { + stockart = undefined; + }); + + it('filled out', function() { + expect(stockart.isPhotoDescriptionRequired).to.be.false; + expect(stockart.isPhotoURLRequired).to.be.false; + expect(stockart.isFileNumberRequired).to.be.false; + }); + + it('empty', function() { + // Reset stockart fields + stockart.description = ''; + stockart.sourceUrl = ''; + stockart.fileNumber = ''; + scope.$digest(); + + expect(stockart.isPhotoDescriptionRequired).to.be.false; + expect(stockart.isPhotoURLRequired).to.be.false; + expect(stockart.isFileNumberRequired).to.be.false; + }); + }); + + + describe('sets the stockart required properties to false when all fields are', function() { + var stockart; + + beforeEach(function() { + stockart = isolateScope.formStockarts[0]; + stockart.description = 'test description'; + stockart.sourceUrl = 'url.com'; + stockart.fileNumber = '123'; + scope.$digest(); + }); + + afterEach(function() { + stockart = undefined; + }); + + it('sets the stockart required properties to true if any field is blank', function() { + // Reset stockart fields + stockart.description = ''; + scope.$digest(); - it('', function() {}); + expect(stockart.isPhotoDescriptionRequired).to.be.true; + expect(stockart.isPhotoURLRequired).to.be.true; + expect(stockart.isFileNumberRequired).to.be.true; + }); + }); }); }); diff --git a/app/directives/tc-input/tc-input.directive.js b/app/directives/tc-input/tc-input.directive.js index ed5a47234..4848e1b33 100644 --- a/app/directives/tc-input/tc-input.directive.js +++ b/app/directives/tc-input/tc-input.directive.js @@ -10,6 +10,7 @@ scope: { labelText: '@', asteriskText: '@', + showAsteriskText: '@', placeholder: '@', inputValue: '=', inputName: '@', diff --git a/app/directives/tc-input/tc-input.jade b/app/directives/tc-input/tc-input.jade index 6140698dd..5208c3309 100644 --- a/app/directives/tc-input/tc-input.jade +++ b/app/directives/tc-input/tc-input.jade @@ -1,15 +1,16 @@ label.tc-label {{labelText}} -p.tc-label__asterisk(ng-if="inputRequired") #[span *]{{asteriskText}} +.tc-label__wrapper + p.tc-label__asterisk(ng-if="inputRequired && showAsteriskText") #[span *]{{asteriskText}} -input( - name="{{inputName}}", - type="{{inputType}}", - placeholder="{{placeholder}}", - ng-model="inputValue", - ng-pattern="inputPattern", - ng-required="inputRequired", - ng-disabled="inputDisabled", - maxlength="{{maxlength}}", - ng-change="onChange()" -) + input( + name="{{inputName}}", + type="{{inputType}}", + placeholder="{{placeholder}}", + ng-model="inputValue", + ng-pattern="inputPattern", + ng-required="inputRequired", + ng-disabled="inputDisabled", + maxlength="{{maxlength}}", + ng-change="onChange()" + ) diff --git a/app/directives/tc-input/tc-input.spec.js b/app/directives/tc-input/tc-input.spec.js new file mode 100644 index 000000000..d7e481353 --- /dev/null +++ b/app/directives/tc-input/tc-input.spec.js @@ -0,0 +1,73 @@ +/* jshint -W117, -W030 */ +describe('Topcoder Input Directive', function() { + var scope, element; + + beforeEach(function() { + bard.appModule('topcoder'); + bard.inject(this, '$compile', '$rootScope'); + scope = $rootScope.$new(); + + element = $compile(angular.element('<tc-input />'))(scope); + scope.$digest(); + }); + + afterEach(function() { + scope.$destroy(); + }); + + bard.verifyNoOutstandingHttpRequests(); + + it('should set inputType to text if no inputType given', function() { + var input = element.find('input')[0]; + + expect(input.type).to.equal('text'); + }); + + it('should set inputType to specified inputType if given', function() { + element = $compile(angular.element('<tc-input input-type="number"/>'))(scope); + scope.$digest(); + + var input = element.find('input')[0]; + + expect(input.type).to.equal('number'); + }); + + it ('should set the inputValue to the result of updateValueOnBlur when blur event is triggered', function() { + scope.updateValueOnBlur = function(inputValue) { + return 'new value and ' + inputValue; + }; + scope.inputValue = 'old value'; + + element = $compile(angular.element('<tc-input input-value="inputValue" update-value-on-blur="updateValueOnBlur(inputValue)"/>'))(scope); + scope.$digest(); + + var input = element.find('input')[0]; + + expect(scope.inputValue).to.equal('old value'); + + $(input).trigger('blur'); + + expect(scope.inputValue).to.equal('new value and old value') + + }); + + it('should pass inputValue and inputName to onInputChange when inputValue changes', function() { + scope.inputValue = 'test input value'; + scope.onInputChange = function(inputValue, inputName) { + return; + }; + + element = $compile(angular.element('<tc-input input-value="inputValue" input-name="\'test input name\'" on-input-change="onInputChange(inputValue, inputName)"/>'))(scope); + scope.$digest(); + + var input = element.find('input')[0]; + var mockOnInputChange = sinon.spy(scope, 'onInputChange'); + + expect(mockOnInputChange).not.calledOnce; + + scope.inputValue = 'new test input value'; + scope.$digest(); + + expect(mockOnInputChange).calledOnce; + }); +}); diff --git a/app/filters/filters.spec.js b/app/filters/filters.spec.js index fc6a6d662..b3e2c9fc9 100644 --- a/app/filters/filters.spec.js +++ b/app/filters/filters.spec.js @@ -131,7 +131,6 @@ describe('filters', function() { expect(ternaryFilter(true, 1, 2)).to.be.equal(1); expect(ternaryFilter(false, 1, 2)).to.be.equal(2); expect(ternaryFilter(0, 1, 2)).to.be.equal(2); - console.log(jstz.determine().name()); expect(ternaryFilter(true, 'm', 'n')).to.be.equal('m'); }); }); diff --git a/app/filters/npad.filter.js b/app/filters/npad.filter.js index ad4081265..b6de18af5 100644 --- a/app/filters/npad.filter.js +++ b/app/filters/npad.filter.js @@ -13,7 +13,7 @@ } if(inputStr.length >= n) return inputStr - var zeros = "0".repeat(n); + var zeros = new Array( n + 1 ).join("0"); return (zeros + inputStr).slice(-1 * n) }; } diff --git a/app/filters/track.filter.js b/app/filters/track.filter.js index 2508a4a23..41b2e2faa 100644 --- a/app/filters/track.filter.js +++ b/app/filters/track.filter.js @@ -9,7 +9,7 @@ var map = { 'UI_PROTOTYPE_COMPETITION': 'UI Prototype Competition', - 'ASSEMBLY_COMPETITION': 'ASSEMBLY', + 'ASSEMBLY_COMPETITION': 'Assembly', 'RIA_BUILD_COMPETITION': 'RIA Build Competition', 'RIA_COMPONENT_COMPETITION': 'RIA Component Competition', 'DEVELOP_MARATHON_MATCH': 'Marathon Match', diff --git a/app/index.jade b/app/index.jade index c9ac97625..906c3de62 100644 --- a/app/index.jade +++ b/app/index.jade @@ -156,8 +156,6 @@ html script(src='../bower_components/react/react.js') script(src='../bower_components/react/react-dom.js') script(src='../bower_components/classnames/index.js') - script(src='../bower_components/classnames/bind.js') - script(src='../bower_components/classnames/dedupe.js') script(src='../bower_components/react-input-autosize/dist/react-input-autosize.min.js') script(src='../bower_components/react-select/dist/react-select.min.js') script(src='../bower_components/ngReact/ngReact.js') @@ -335,7 +333,8 @@ html script(src="submissions/submissions.module.js") script(src="submissions/submissions.controller.js") script(src="submissions/submissions.routes.js") - script(src="submissions/submit-file/submit-file.controller.js") + script(src="submissions/submit-design-files/submit-design-files.controller.js") + script(src="submissions/submit-develop-files/submit-develop-files.controller.js") script(src="topcoder.constants.js") script(src="topcoder.controller.js") script(src="topcoder.interceptors.js") diff --git a/app/my-dashboard/programs/programs.controller.js b/app/my-dashboard/programs/programs.controller.js index b6cbe3aa7..cb192afb8 100644 --- a/app/my-dashboard/programs/programs.controller.js +++ b/app/my-dashboard/programs/programs.controller.js @@ -49,7 +49,6 @@ function registerUser() { - debugger; vm.loading = true; return MemberCertService.registerMember(userId, CONSTANTS.SWIFT_PROGRAM_ID).then(function(data) { if (data && data.eventId && data.userId) { diff --git a/app/profile/subtrack/design/design-challenges.jade b/app/profile/subtrack/design/design-challenges.jade index 15a6bf01a..dc8b7260d 100644 --- a/app/profile/subtrack/design/design-challenges.jade +++ b/app/profile/subtrack/design/design-challenges.jade @@ -8,4 +8,4 @@ .no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0") | Sorry, no successful challenges found. - tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams") + tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams") diff --git a/app/profile/subtrack/develop/develop-challenges.jade b/app/profile/subtrack/develop/develop-challenges.jade index 6214ffc5b..da7a5154b 100644 --- a/app/profile/subtrack/develop/develop-challenges.jade +++ b/app/profile/subtrack/develop/develop-challenges.jade @@ -10,4 +10,4 @@ .no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0") | Sorry, no successful challenges found. - tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams") + tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams") diff --git a/app/profile/subtrack/subtrack.controller.js b/app/profile/subtrack/subtrack.controller.js index 7433df47d..e773453ad 100644 --- a/app/profile/subtrack/subtrack.controller.js +++ b/app/profile/subtrack/subtrack.controller.js @@ -25,7 +25,7 @@ vm.back = back; vm.subTrackStats = []; - vm.pageName = vm.subTrack.toLowerCase().replace(/_/g, ' '); + vm.pageName = vm.subTrack; vm.tabs = ['statistics']; diff --git a/app/profile/subtrack/subtrack.jade b/app/profile/subtrack/subtrack.jade index 9e83453a2..a3f1bd945 100644 --- a/app/profile/subtrack/subtrack.jade +++ b/app/profile/subtrack/subtrack.jade @@ -1,5 +1,5 @@ .profile-subtrack-container(ng-cloak, ng-show="profileVm.status.stats === 'ready'") - .content + .content .page-header page-state-header(handle="{{vm.userHandle}}", page-title="{{vm.pageName}}", hide-money="true", show-back-link="true", default-state="profile") .nav-right @@ -11,7 +11,7 @@ .carousel-elem a.flex-wrapper(ng-class="{'link': item.link, 'no-link': !item.link}", ng-href="{{item.link}}") p.title(ng-hide="true") {{item.subTrack | track}} - + p.value(ng-show="item.label === 'rating'", style="color: {{item.val | ratingColor}}") {{item.val | empty}} span(style="background-color: {{item.val | ratingColor}}") @@ -22,20 +22,20 @@ include ./develop/develop-statistics.jade include ./design/design-statistics.jade include ./data/data-statistics.jade - + tc-tab(heading="{{vm.tabs[1]}}") .subtrack-stats responsive-carousel(data="vm.subTrackStats", handle="{{vm.handle}}") .carousel-elem .flex-wrapper p.title(ng-hide="true") {{item.subTrack | track}} - + p.value(ng-show="item.label === 'rating'", style="color: {{item.val | ratingColor}}") {{item.val | empty}} span(style="background-color: {{item.val | ratingColor}}") p.value(ng-hide="item.label === 'rating'") {{item.val | empty}} p.label {{item.label}} - include ./develop/develop-challenges.jade - include ./design/design-challenges.jade + include ./develop/develop-challenges.jade + include ./design/design-challenges.jade include ./data/data-challenges.jade diff --git a/app/services/challenge.service.js b/app/services/challenge.service.js index eba8c1a3b..6d86d4628 100644 --- a/app/services/challenge.service.js +++ b/app/services/challenge.service.js @@ -80,7 +80,7 @@ // if user has role of observer var roles = _.get(challenge, 'userDetails.roles', []); - if (roles.length > 0) { + if (roles && roles.length > 0) { var submitterRole = _.findIndex(roles, function(role) { var lRole = role.toLowerCase(); return lRole === 'submitter'; @@ -189,7 +189,8 @@ return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST && submission.placement; }), 'placement').placement; } - if (challenge.track === 'DEVELOP' && challenge.subTrack === 'FIRST_2_FINISH') { + if (challenge.track === 'DEVELOP' && challenge.subTrack === 'FIRST_2_FINISH' + && challenge.userDetails.submissions && challenge.userDetails.submissions.length > 0) { challenge.highestPlacement = _.min(challenge.userDetails.submissions.filter(function(submission) { return submission.type === CONSTANTS.SUBMISSION_TYPE_CONTEST && submission.status === CONSTANTS.STATUS_ACTIVE && submission.placement; diff --git a/app/services/challenge.service.spec.js b/app/services/challenge.service.spec.js index 142dd8786..1de3f7ec0 100644 --- a/app/services/challenge.service.spec.js +++ b/app/services/challenge.service.spec.js @@ -40,357 +40,762 @@ describe('Challenge Service', function() { $httpBackend.flush(); }); - it('processPastChallenges should process the won DESIGN/WEB_DESIGNS challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DESIGN', - subTrack: 'WEB_DESIGNS', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ - { - challengeId: 30041345, - id: 12345, - placement: 1, - score: 98.0, - status: 'Active', - type: 'Contest Submission' - }, + describe('processActiveDevDesignChallenges ', function() { + it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12346, - placement: 11, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12347, - placement: 21, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null } - ] + ], + userDetails: { + roles: [ + "Submitter" + ], + hasUserSubmittedForReview: false, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).to.exist.to.equal(1); - expect(challenge.wonFirst).to.exist.to.true; - expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).to.exist.to.equal("Submit"); + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Registration'); + }); - it('processPastChallenges should process the won DEVELOP/<ANY> challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'CODE', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ - { - challengeId: 30041345, - id: 12345, - placement: 1, - score: 98.0, - status: 'Active', - type: 'Contest Submission' - }, + it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role with 1 hour difference in end date ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12346, - placement: 11, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().subtract(5, 'days'), + "scheduledEndTime": moment().add(1, 'hours'), + "actualStartTime": moment().subtract(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12347, - placement: 21, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null } ], - winningPlacements: [2, 11, 1] + userDetails: { + roles: [ + "Submitter" + ], + hasUserSubmittedForReview: false, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).to.exist.to.equal("Submit"); + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('1'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('hour'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Registration'); + }); - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).to.exist.to.equal(1); - expect(challenge.wonFirst).to.exist.to.true; - expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); - - it('processPastChallenges should process the lost DEVELOP/<ANY> challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'CODE', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ - { - challengeId: 30041345, - id: 12345, - placement: 1, - score: 98.0, - status: 'Active', - type: 'Contest Submission' - }, + it('should process the active DESIGN/WEB_DESIGNS challenge without null role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12346, - placement: 11, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12347, - placement: 21, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null } ], - winningPlacements: [0] + userDetails: { + roles: null, + hasUserSubmittedForReview: false, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).to.exist.to.equal("Submit"); + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Registration'); + }); - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).not.to.exist; - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); - - it('processPastChallenges should process the won DEVELOP/FIRST_2_FINISH challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ - { - challengeId: 30041345, - id: 12345, - placement: 1, - score: 98.0, - status: 'Active', - type: 'Contest Submission' - }, + it('should process the active DESIGN/WEB_DESIGNS challenge without undefined role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12346, - placement: 11, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12347, - placement: 21, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null } - ] + ], + userDetails: { + hasUserSubmittedForReview: false, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).to.exist.to.equal(1); - expect(challenge.wonFirst).to.exist.to.true; - expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).to.exist.to.equal("Submit"); + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Registration'); + }); - it('processPastChallenges should process the lost(without placement) DEVELOP/FIRST_2_FINISH challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ + it('should process the active DESIGN/WEB_DESIGNS challenge with submitter role and already submitted ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12345, - placement: null, - score: 34.0, - status: 'Active', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12346, - placement: null, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null + } + ], + userDetails: { + roles: [ + "Submitter" + ], + hasUserSubmittedForReview: true, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } + } + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).to.exist.to.equal("Submitted"); + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('20'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Submission'); + }); + + it('should process the active DESIGN/WEB_DESIGNS challenge with non submitter role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ + { + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12347, - placement: 1, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null } - ] + ], + userDetails: { + roles: [ + "Observer" + ], + hasUserSubmittedForReview: false, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).not.to.exist; - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).not.to.exist; + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('10'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Registration'); + }); - it('processPastChallenges should process the lost(with placement) DEVELOP/FIRST_2_FINISH challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: true, - roles: ['Submitter'], - submissions: [ + it('should process the active DESIGN/WEB_DESIGNS challenge with non submitter role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + currentPhases: [ { - challengeId: 30041345, - id: 12345, - placement: 5, - score: 34.0, - status: 'Active', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789719, + "phaseType": "Registration", + "phaseStatus": "Open", + "duration": 2419200000, + "scheduledStartTime": moment().add(5, 'days'), + "scheduledEndTime": moment().add(10, 'days'), + "actualStartTime": moment().add(5, 'days'), + "actualEndTime": null, + "fixedStartTime": "2016-01-15T18:00Z" }, { - challengeId: 30041345, - id: 12346, - placement: null, - score: 0.0, - status: 'Failed Review', - type: 'Contest Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Submission", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(6, 'days'), + "scheduledEndTime": moment().add(20, 'days'), + "actualStartTime": moment().add(6, 'days'), + "actualEndTime": null, + "fixedStartTime": null }, { - challengeId: 30041345, - id: 12347, - placement: 1, - score: 0.0, - status: 'Completed Without Win', - type: 'Checkpoint Submission' + "challengeId": 30052661, + "id": 789720, + "phaseType": "Review", + "phaseStatus": "Open", + "duration": 2418900000, + "scheduledStartTime": moment().add(20, 'days'), + "scheduledEndTime": moment().add(22, 'days'), + "actualStartTime": moment().add(20, 'days'), + "actualEndTime": null, + "fixedStartTime": null } - ] + ], + userDetails: { + roles: [ + "Submitter" + ], + hasUserSubmittedForReview: true, + submissionReviewScore: null, + winningPlacements: null, + submissions: null + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).to.exist.to.equal(5); - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; + ]; + ChallengeService.processActiveDevDesignChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.userAction).not.to.exist; + expect(challenge.userCurrentPhaseEndTime).to.exist.to.have.length(3); + expect(challenge.userCurrentPhaseEndTime[0]).to.exist.to.equal('22'); + expect(challenge.userCurrentPhaseEndTime[1]).to.exist.to.equal('days'); + expect(challenge.userCurrentPhase).to.exist.to.equal('Review'); + }); }); - it('processPastChallenges should process a not completed DEVELOP/FIRST_2_FINISH challenge ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: false, - roles: ['Submitter'], - submissions: [] + describe('processPastChallenges ', function() { + it('should process the won DESIGN/WEB_DESIGNS challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DESIGN', + subTrack: 'WEB_DESIGNS', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: 1, + score: 98.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: 11, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 21, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ] + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).not.to.exist; - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED'); - expect(challenge.userHasSubmitterRole).to.exist.to.true; - }); + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).to.exist.to.equal(1); + expect(challenge.wonFirst).to.exist.to.true; + expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process the won DEVELOP/<ANY> challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'CODE', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: 1, + score: 98.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: 11, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 21, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ], + winningPlacements: [2, 11, 1] + } - it('processPastChallenges should process a DEVELOP/FIRST_2_FINISH challenge for a non submitter user ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: false, - roles: ['Observer'], - submissions: [] } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).not.to.exist; - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('COMPLETED'); - expect(challenge.userHasSubmitterRole).to.exist.to.false; - }); + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).to.exist.to.equal(1); + expect(challenge.wonFirst).to.exist.to.true; + expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process the lost DEVELOP/<ANY> challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'CODE', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: 1, + score: 98.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: 11, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 21, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ], + winningPlacements: [0] + } + + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process the won DEVELOP/FIRST_2_FINISH challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: 1, + score: 98.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: 11, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 21, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).to.exist.to.equal(1); + expect(challenge.wonFirst).to.exist.to.true; + expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process the lost(without placement) DEVELOP/FIRST_2_FINISH challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: null, + score: 34.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: null, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 1, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('PASSED_SCREENING'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); - it('processPastChallenges should process a DEVELOP/<ANY> challenge for a user without role ', function() { - var challenges = [ - { - id: 30041345, - name: 'Mock Challenge 1', - track: 'DEVELOP', - subTrack: 'FIRST_2_FINISH', - userDetails: { - hasUserSubmittedForReview: false, - roles: [], - submissions: [], - winningPlacements: [0] + it('should process the lost(with placement) DEVELOP/FIRST_2_FINISH challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: true, + roles: ['Submitter'], + submissions: [ + { + challengeId: 30041345, + id: 12345, + placement: 5, + score: 34.0, + status: 'Active', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12346, + placement: null, + score: 0.0, + status: 'Failed Review', + type: 'Contest Submission' + }, + { + challengeId: 30041345, + id: 12347, + placement: 1, + score: 0.0, + status: 'Completed Without Win', + type: 'Checkpoint Submission' + } + ] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).to.exist.to.equal(5); + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('PASSED_REVIEW'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process a not completed (empty submissions) DEVELOP/FIRST_2_FINISH challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: false, + roles: ['Submitter'], + submissions: [] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process a not completed(null submissions) DEVELOP/FIRST_2_FINISH challenge ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: false, + roles: ['Submitter'], + submissions: null + } } - } - ]; - ChallengeService.processPastChallenges(challenges); - var challenge = challenges[0]; - expect(challenge.highestPlacement).not.to.exist; - expect(challenge.wonFirst).to.exist.to.false; - expect(challenge.userStatus).to.exist.to.equal('COMPLETED'); - expect(challenge.userHasSubmitterRole).to.exist.to.false; + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('NOT_FINISHED'); + expect(challenge.userHasSubmitterRole).to.exist.to.true; + }); + + it('should process a DEVELOP/FIRST_2_FINISH challenge for a non submitter user ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: false, + roles: ['Observer'], + submissions: [] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('COMPLETED'); + expect(challenge.userHasSubmitterRole).to.exist.to.false; + }); + + it('should process a DEVELOP/<ANY> challenge for a user without role ', function() { + var challenges = [ + { + id: 30041345, + name: 'Mock Challenge 1', + track: 'DEVELOP', + subTrack: 'FIRST_2_FINISH', + userDetails: { + hasUserSubmittedForReview: false, + roles: [], + submissions: [], + winningPlacements: [0] + } + } + ]; + ChallengeService.processPastChallenges(challenges); + var challenge = challenges[0]; + expect(challenge.highestPlacement).not.to.exist; + expect(challenge.wonFirst).to.exist.to.false; + expect(challenge.userStatus).to.exist.to.equal('COMPLETED'); + expect(challenge.userHasSubmitterRole).to.exist.to.false; + }); + }); it('processPastSRM should process SRM with valid placement ', function() { diff --git a/app/services/communityData.service.js b/app/services/communityData.service.js index ee3dd03c6..75050d1d7 100644 --- a/app/services/communityData.service.js +++ b/app/services/communityData.service.js @@ -19,28 +19,28 @@ var data = { "memberLeaderboard": [ { - "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/abedavera.jpg", - "name": "abedavera", + "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/nexttopdesigns_dec2015.png", + "name": "nexttopdesigns", "contestType": "Design", - "description": "TCO15 Design Champion!", + "description": "Ten wins earning over $10K in design challenges", "class": "design" }, { - "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/bonton.jpg", - "name": "bonton", + "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/seriyvolk83_dec2015.png", + "name": "seriyvolk83", "contestType": "Development", - "description": "Won $4200 with 4 wins in development challenges", + "description": "Six wins earning over $4K in development challenges", "class": "develop" }, { - "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/maksay.jpg", - "name": "maksay", + "avatar": "//www.topcoder.com/wp-content/uploads/2015/05/sadhwaniyash6_dec2015.png", + "name": "sadhwaniyash6", "contestType": "Data Science", - "description": "Only participant to see a ratings increase in all four rounds in Sept.", + "description": "Rating increase of 627 pts in Oct SRMs vaulting into Div 1.", "class": "data-science" }, { - "avatar": "https://www.topcoder.com/wp-content/uploads/2015/05/herlansyahs.jpg", - "name": "herlansyahs", + "avatar": "https://www.topcoder.com/wp-content/uploads/2015/05/alyad_dec2015.png", + "name": "Alyad", "contestType": "Design Rookie", - "description": "Won first and second placements within his first two months of becoming a member!", + "description": "Won 2 challenges within 6 weeks of becoming a member!", "class": "design" }], "copilots": [{ diff --git a/app/services/helpers.service.js b/app/services/helpers.service.js index d04ace38f..04da0a983 100644 --- a/app/services/helpers.service.js +++ b/app/services/helpers.service.js @@ -156,7 +156,7 @@ answer: '' + q.answer }; - if (q.comment.length > 0) { + if (q.comment && q.comment.length > 0) { reviewItem.comments = [ { content: '' + q.comment, @@ -234,7 +234,7 @@ }); // now loop over all keys and replace with compiled value Object.keys(compiledMap).forEach(function(k) { - template = template.replace(k, compiledMap[k]) + template = template.replace(k, (compiledMap[k] ? compiledMap[k] : '')); }); } return template; @@ -298,8 +298,8 @@ } function setupLoginEventMetrics (usernameOrEmail) { - if (_kmq) { - _kmq.push(['identify', usernameOrEmail ]); + if ($window._kmq) { + $window._kmq.push(['identify', usernameOrEmail ]); } } diff --git a/app/services/helpers.service.spec.js b/app/services/helpers.service.spec.js index 9c797c34a..ea9e2e550 100644 --- a/app/services/helpers.service.spec.js +++ b/app/services/helpers.service.spec.js @@ -4,6 +4,9 @@ describe('Helper Service', function() { var fakeWindow = { location: { href: "/" + }, + decodeURIComponent: function(param) { + return decodeURIComponent(param); } }; // sinon.spy(fakeWindow.location, "href"); @@ -19,13 +22,13 @@ describe('Helper Service', function() { sinon.spy(fakeState, "go"); beforeEach(function() { - module('tc.services', function($provide) { + module('topcoder', function($provide) { $provide.value('$window', fakeWindow); $provide.value('$state', fakeState); $provide.value('$location', fakeLocation); }); - bard.inject(this, 'Helpers', '$state', '$location'); + bard.inject(this, 'Helpers', '$rootScope', '$state', '$location', '$window', '$httpBackend'); }); describe("isEmail()", function() { @@ -54,4 +57,358 @@ describe('Helper Service', function() { expect($location.url).to.have.been.calledWith("/members/test1/"); }); }); + + describe("getSocialUserData()", function() { + var mockProfile; + beforeEach(function() { + mockProfile = mockData.getMockAuth0Profile(); + }); + it("should get JSON for facebook user data ", function() { + mockProfile.identities[0].connection = 'facebook'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.first_name + '.' + mockProfile.last_name); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('facebook'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for github user data ", function() { + mockProfile.identities[0].connection = 'github'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.nickname); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('github'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for github user data without lastname ", function() { + mockProfile.identities[0].connection = 'github'; + mockProfile.name = 'mock'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.nickname); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(''); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('github'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for bitbucket user data ", function() { + mockProfile.identities[0].connection = 'bitbucket'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.username); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('bitbucket'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for stackoverflow user data ", function() { + mockProfile.identities[0].connection = 'stackoverflow'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal('123456'); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('stackoverflow'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for dribbble user data ", function() { + mockProfile.identities[0].connection = 'dribbble'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal('123456'); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('dribbble'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for twitter user data ", function() { + mockProfile.identities[0].connection = 'twitter'; + mockProfile.screen_name = mockProfile.username; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.username); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + // Twitter does not give email + expect(socialData.email).to.exist.to.equal(''); + expect(socialData.socialProvider).to.exist.to.equal('twitter'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for twitter user data without lastname ", function() { + mockProfile.identities[0].connection = 'twitter'; + mockProfile.name = 'mock'; + mockProfile.screen_name = mockProfile.username; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.username); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(''); + // Twitter does not give email + expect(socialData.email).to.exist.to.equal(''); + expect(socialData.socialProvider).to.exist.to.equal('twitter'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + + it("should get JSON for google-oauth2 user data ", function() { + mockProfile.identities[0].connection = 'google-oauth2'; + var socialData = Helpers.getSocialUserData(mockProfile, ""); + expect(socialData).to.exist.not.null; + expect(socialData.socialUserId).to.exist.to.equal('123456'); + // TODO cross check population of username for all networks + expect(socialData.username).to.exist.to.equal(mockProfile.nickname); + expect(socialData.firstname).to.exist.to.equal(mockProfile.first_name); + expect(socialData.lastname).to.exist.to.equal(mockProfile.last_name); + expect(socialData.email).to.exist.to.equal(mockProfile.email); + expect(socialData.socialProvider).to.exist.to.equal('google-oauth2'); + expect(socialData.accessToken).to.exist.to.equal(mockProfile.identities[0].access_token); + expect(socialData.accessTokenSecret).to.exist.to.equal(mockProfile.identities[0].access_token_secret); + }); + }); + + describe("getPageTitle()", function() { + + it("should get page title from state ", function() { + var state = { data: {title: 'Mock Page'}}; + var title = Helpers.getPageTitle(state, null); + expect(title).to.exist.to.equal('Mock Page | TopCoder'); + }); + + it("should get default page title when state does not have page title ", function() { + var state = {}; + var title = Helpers.getPageTitle(state, null); + expect(title).to.exist.to.equal('TopCoder'); + }); + + it("should get page title from state with dynamic data ", function() { + var state = { data: {title: 'Mock Page {{a.b.c}}'}}; + var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {c: 'Title'}}}}}}); + expect(title).to.exist.to.equal('Mock Page Title | TopCoder'); + }); + + it("should get static page title from state with unknown expression for dynamic data ", function() { + var state = { data: {title: 'Mock Page {a.b.c}'}}; + var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {c: 'Title'}}}}}}); + expect(title).to.exist.to.equal('Mock Page {a.b.c} | TopCoder'); + }); + + it("should replace dynamic data with empty value when not available in current state ", function() { + var state = { data: {title: 'Mock Page {{a.b.c}}'}}; + var title = Helpers.getPageTitle(state, {locals : {resolve: {$$values : {a: {b : {}}}}}}); + expect(title).to.exist.to.equal('Mock Page | TopCoder'); + }); + }); + + describe("getParameterByName()", function() { + + it("should get params from the URL ", function() { + var url = "https://topcoder.com/challenges?paramA=123¶mB=234"; + var paramA = Helpers.getParameterByName('paramA', url); + var paramB = Helpers.getParameterByName('paramB', url); + expect(paramA).to.exist.to.equal('123'); + expect(paramB).to.exist.to.equal('234'); + }); + + it("should get params with [ or ] in from the URL ", function() { + var url = "https://topcoder.com/challenges?[paramA]=123&[paramB]=234"; + var paramA = Helpers.getParameterByName('[paramA]', url); + var paramB = Helpers.getParameterByName('[paramB]', url); + expect(paramA).to.exist.to.equal('123'); + expect(paramB).to.exist.to.equal('234'); + }); + + it("should get empty value for non existing param from the URL ", function() { + var url = "https://topcoder.com/challenges?paramA=123¶mB=234"; + var paramA = Helpers.getParameterByName('paramC', url); + expect(paramA).to.exist.to.equal(''); + }); + }); + + describe("peerReview module helpers ", function() { + it("should store objects by id ", function() { + var obj = {}; + var questions = [{id:1}, {id: 'bcd'}, {id: 'cde'}]; + Helpers.storeById(obj, questions); + expect(obj['1']).to.exist; + expect(obj.bcd).to.exist; + expect(obj.cde).to.exist; + }); + + it("should parse questions ", function() { + var questions = [ + {id: 1, questionTypeId: 5, guideline: 'some guideline'}, + {id: 'bcd', questionTypeId: 4, guideline: 'some guideline\nsecond line'}, + {id: 'cde', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'} + ]; + Helpers.parseQuestions(questions); + expect(questions[0].guidelines).to.exist.to.have.length(1); + expect(questions[1].guidelines).not.to.exist; + expect(questions[2].guidelines).to.exist.to.have.length(3); + }); + + it("should parse answers ", function() { + var questions = { + q1 : {id: 'q1', questionTypeId: 5, guideline: 'some guideline'}, + q2 : {id: 'q2', questionTypeId: 5, guideline: 'some guideline\nsecond line'}, + q3 : {id: 'q3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'} + }; + var answers = [ + {id: 'a1', scorecardQuestionId: 'q1', answer: 3, comments:[ {content: 'perfect'}]}, + {id: 'a2', scorecardQuestionId: 'q2', answer: 1, comments:[]}, + {id: 'a3', scorecardQuestionId: 'q3', answer: 2, comments:[ {content: 'good'}]} + ]; + Helpers.parseAnswers(questions, answers); + // validate q1 + expect(questions.q1.answer).to.exist.to.equal(3); + expect(questions.q1.reviewItemId).to.exist.to.equal('a1'); + expect(questions.q1.comment).to.exist.to.equal('perfect'); + // validate q2 + expect(questions.q2.answer).to.exist.to.equal(1); + expect(questions.q2.reviewItemId).to.exist.to.equal('a2'); + expect(questions.q2.comment).not.to.exist; + // validate q3 + expect(questions.q3.answer).to.exist.to.equal(2); + expect(questions.q3.reviewItemId).to.exist.to.equal('a3'); + expect(questions.q3.comment).to.exist.to.equal('good'); + }); + + it("should compile review items for first time creation ", function() { + var questions = { + '1' : {id: '1', questionTypeId: 5, guideline: 'some guideline'}, + '2' : {id: '2', questionTypeId: 5, guideline: 'some guideline\nsecond line'}, + '3' : {id: '3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line'} + }; + var answers = [ + {id: 'a1', scorecardQuestionId: '1', answer: 3, comments:[ {content: 'perfect'}]}, + {id: 'a2', scorecardQuestionId: '2', answer: 1, comments:[]}, + {id: 'a3', scorecardQuestionId: '3', answer: 2, comments:[ {content: 'good'}]} + ]; + // assumes parseAnswers to be working as expected + Helpers.parseAnswers(questions, answers); + + var review = {id: 'rev1', resourceId: 'res1', uploadId: 'u1'}; + var reviewItems = Helpers.compileReviewItems(questions, review, false); + expect(reviewItems).to.exist.to.have.length(3); + expect(reviewItems[0].reviewId).to.exist.to.equal(review.id); + expect(reviewItems[0].uploadId).to.exist.to.equal(review.uploadId); + // expect(reviewItems[0].id).to.exist.to.equal(answers[0].id); + expect(reviewItems[0].answer).to.exist.to.equal(answers[0].answer.toString()); + expect(reviewItems[0].scorecardQuestionId).to.exist.to.equal(parseInt(answers[0].scorecardQuestionId)); + expect(reviewItems[0].comments).to.exist.to.have.length(answers[0].comments.length); + }); + + it("should compile review items for updating existing review items ", function() { + var questions = { + '1' : {id: '1', questionTypeId: 5, guideline: 'some guideline', reviewItemId: 'revItem1'}, + '2' : {id: '2', questionTypeId: 5, guideline: 'some guideline\nsecond line', reviewItemId: 'revItem2'}, + '3' : {id: '3', questionTypeId: 5, guideline: 'some guideline\nsecond line\nthird line', reviewItemId: 'revItem3'} + }; + var answers = [ + {id: 'a1', scorecardQuestionId: '1', answer: 3, comments:[ {content: 'perfect'}]}, + {id: 'a2', scorecardQuestionId: '2', answer: 1, comments:[]}, + {id: 'a3', scorecardQuestionId: '3', answer: 2, comments:[ {content: 'good'}]} + ]; + // assumes parseAnswers to be working as expected + Helpers.parseAnswers(questions, answers); + + var review = {id: 'rev1', resourceId: 'res1', uploadId: 'u1'}; + var reviewItems = Helpers.compileReviewItems(questions, review, true); + expect(reviewItems).to.exist.to.have.length(3); + expect(reviewItems[0].uploadId).to.exist.to.equal(review.uploadId); + expect(reviewItems[0].id).to.exist.to.equal(answers[0].id); + expect(reviewItems[0].answer).to.exist.to.equal(answers[0].answer.toString()); + expect(reviewItems[0].scorecardQuestionId).to.exist.to.equal(parseInt(answers[0].scorecardQuestionId)); + expect(reviewItems[0].comments).to.exist.to.have.length(answers[0].comments.length); + }); + }); + + describe("npad ", function() { + it("should pad string with 0 ", function() { + var padded = Helpers.npad("123", 5); + expect(padded).to.exist.to.equal('00123'); + }); + + it("should pad number with 0 ", function() { + var padded = Helpers.npad(123, 5); + expect(padded).to.exist.to.equal('00123'); + }); + + it("should not pad string with 0 ", function() { + var padded = Helpers.npad("12345", 5); + expect(padded).to.exist.to.equal('12345'); + }); + }); + + describe("setupLoginEventMetrics ", function() { + it("should add object with identify key ", function() { + $window._kmq = []; + Helpers.setupLoginEventMetrics('mockuser'); + expect($window._kmq).to.have.length(1); + expect($window._kmq[0][0]).to.exist.to.equal('identify'); + expect($window._kmq[0][1]).to.exist.to.equal('mockuser'); + }); + }); + + xdescribe("getCountyObjFromIP ", function() { + it("should get valid country object ", function() { + var mockLocation = { + "ip": "123.63.151.213", + "hostname": "No Hostname", + "city": "New Delhi", + "region": "National Capital Territory of Delhi", + "country": "IN", + "loc": "28.6000,77.2000", + "org": "Mock Organization" + }; + + $httpBackend + .when('GET', 'http://ipinfo.io') + .respond(200, mockLocation); + + $rootScope.$apply(); + console.log(Helpers.getCountyObjFromIP().then(function(data) { + console.log(data); + })); + $rootScope.$apply(); + }); + }); }); diff --git a/app/specs.html b/app/specs.html index e560f835f..982ab2f85 100644 --- a/app/specs.html +++ b/app/specs.html @@ -44,6 +44,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> </script> <!-- bower:js --> + <script src="../bower_components/zepto/zepto.js"></script> <script src="../bower_components/angular/angular.js"></script> <script src="../bower_components/a0-angular-storage/dist/angular-storage.js"></script> <script src="../bower_components/angucomplete-alt/angucomplete-alt.js"></script> @@ -63,13 +64,24 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="../bower_components/angular-animate/angular-animate.js"></script> <script src="../bower_components/angularjs-toaster/toaster.js"></script> <script src="../bower_components/appirio-tech-ng-iso-constants/dist/ng-iso-constants.js"></script> + <script src="../bower_components/angular-resource/angular-resource.js"></script> + <script src="../bower_components/moment/moment.js"></script> + <script src="../bower_components/angular-scroll/angular-scroll.js"></script> + <script src="../bower_components/react/react.js"></script> + <script src="../bower_components/react/react-dom.js"></script> + <script src="../bower_components/classnames/index.js"></script> + <script src="../bower_components/classnames/bind.js"></script> + <script src="../bower_components/classnames/dedupe.js"></script> + <script src="../bower_components/react-input-autosize/dist/react-input-autosize.min.js"></script> + <script src="../bower_components/react-select/dist/react-select.min.js"></script> + <script src="../bower_components/ngReact/ngReact.js"></script> + <script src="../bower_components/appirio-tech-ng-ui-components/dist/main.js"></script> <script src="../bower_components/d3/d3.js"></script> <script src="../bower_components/jstzdetect/jstz.min.js"></script> - <script src="../bower_components/moment/moment.js"></script> + <script src="../bower_components/lodash/lodash.js"></script> <script src="../bower_components/ng-busy/build/angular-busy.js"></script> <script src="../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.js"></script> <script src="../bower_components/ngDialog/js/ngDialog.js"></script> - <script src="../bower_components/lodash/lodash.js"></script> <script src="../bower_components/restangular/dist/restangular.js"></script> <script src="../bower_components/angular-touch/angular-touch.js"></script> <script src="../bower_components/angular-carousel/dist/angular-carousel.js"></script> @@ -80,6 +92,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="../bower_components/bardjs/dist/bard.js"></script> <script src="../bower_components/bardjs/dist/bard-ngRouteTester.js"></script> <script src="../bower_components/jquery/dist/jquery.js"></script> + <script src="../bower_components/bind-polyfill/index.js"></script> <!-- endbower --> <!-- inject:nonBowerScripts:js --> @@ -95,6 +108,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/topcoder.controller.js"></script> <script src="/app/topcoder.constants.js"></script> <script src="/app/submissions/submissions.module.js"></script> + <script src="/app/submissions/submit-design-files/submit-design-files.controller.js"></script> <script src="/app/submissions/submissions.routes.js"></script> <script src="/app/submissions/submissions.controller.js"></script> <script src="/app/skill-picker/skill-picker.module.js"></script> @@ -113,6 +127,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/services/user.service.js"></script> <script src="/app/services/tcAuth.service.js"></script> <script src="/app/services/tags.service.js"></script> + <script src="/app/services/submissions.service.js"></script> <script src="/app/services/statistics.service.js"></script> <script src="/app/services/srm.service.js"></script> <script src="/app/services/scorecard.service.js"></script> @@ -184,20 +199,29 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/filters/empty.filter.js"></script> <script src="/app/filters/deadline-msg.filter.js"></script> <script src="/app/filters/challengeLinks.filter.js"></script> + <script src="/app/filters/add-beginning-space.filter.js"></script> <script src="/app/directives/tcui-components.module.js"></script> <script src="/app/directives/track-toggle/track-toggle.directive.js"></script> <script src="/app/directives/tc-transclude.directive.js"></script> + <script src="/app/directives/tc-textarea/tc-textarea.directive.js"></script> <script src="/app/directives/tc-tabs/tc-tabs.directive.js"></script> <script src="/app/directives/tc-sticky/tc-sticky.directive.js"></script> <script src="/app/directives/tc-section/tc-section.directive.js"></script> <script src="/app/directives/tc-paginator/tc-paginator.directive.js"></script> + <script src="/app/directives/tc-input/tc-input.directive.js"></script> + <script src="/app/directives/tc-form-stockart/tc-form-stockart.directive.js"></script> + <script src="/app/directives/tc-form-fonts/tc-form-fonts.directive.js"></script> + <script src="/app/directives/tc-file-input/tc-file-input.directive.js"></script> <script src="/app/directives/tc-endless-paginator/tc-endless-paginator.directive.js"></script> <script src="/app/directives/srm-tile/srm-tile.directive.js"></script> <script src="/app/directives/slideable.directive.js"></script> <script src="/app/directives/skill-tile/skill-tile.directive.js"></script> <script src="/app/directives/responsive-carousel/responsive-carousel.directive.js"></script> + <script src="/app/directives/progress-bar/progress-bar.directive.js"></script> <script src="/app/directives/profile-widget/profile-widget.directive.js"></script> + <script src="/app/directives/preventEventPropagation.directive.js"></script> <script src="/app/directives/page-state-header/page-state-header.directive.js"></script> + <script src="/app/directives/onoffswitch/onoffswitch.directive.js"></script> <script src="/app/directives/on-file-change.directive.js"></script> <script src="/app/directives/ios-card/ios-card.directive.js"></script> <script src="/app/directives/input-sticky-placeholder/input-sticky-placeholder.directive.js"></script> @@ -206,6 +230,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/directives/focus-on.directive.js"></script> <script src="/app/directives/external-account/external-web-links.directive.js"></script> <script src="/app/directives/external-account/external-links-data.directive.js"></script> + <script src="/app/directives/external-account/external-link-deletion.controller.js"></script> <script src="/app/directives/external-account/external-account.directive.js"></script> <script src="/app/directives/empty-state-placeholder/empty-state-placeholder.directive.js"></script> <script src="/app/directives/distribution-graph/distribution-graph.directive.js"></script> @@ -247,7 +272,6 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/my-dashboard/my-dashboard.spec.js"></script> <script src="/app/my-srms/my-srms.spec.js"></script> <script src="/app/profile/profile.controller.spec.js"></script> - <script src="/app/settings/settings.spec.js"></script> <script src="/app/services/authToken.service.spec.js"></script> <script src="/app/services/challenge.service.spec.js"></script> <script src="/app/services/externalAccounts.service.spec.js"></script> @@ -258,29 +282,35 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/services/tcAuth.service.spec.js"></script> <script src="/app/services/user.service.spec.js"></script> <script src="/app/services/userStats.service.spec.js"></script> + <script src="/app/settings/settings.spec.js"></script> <script src="/app/skill-picker/skill-picker.spec.js"></script> <script src="/app/submissions/submissions.spec.js"></script> <script src="/app/account/login/login.spec.js"></script> <script src="/app/account/logout/logout.controller.spec.js"></script> - <script src="/app/account/register/register.spec.js"></script> <script src="/app/account/reset-password/reset-password.spec.js"></script> + <script src="/app/account/register/register.spec.js"></script> <script src="/app/blocks/exception/exception-handler.provider.spec.js"></script> <script src="/app/directives/badges/badge-tooltip.spec.js"></script> <script src="/app/directives/busy-button/busy-button.directive.spec.js"></script> <script src="/app/directives/challenge-tile/challenge-tile.spec.js"></script> <script src="/app/directives/empty-state-placeholder/empty-state-placeholder.spec.js"></script> <script src="/app/directives/external-account/external-account.directive.spec.js"></script> + <script src="/app/directives/external-account/external-link-deletion.controller.spec.js"></script> <script src="/app/directives/external-account/external-links-data.directive.spec.js"></script> <script src="/app/directives/external-account/external-web-links.directive.spec.js"></script> <script src="/app/directives/tc-endless-paginator/tc-endless-paginator.spec.js"></script> + <script src="/app/directives/tc-file-input/tc-file-input.spec.js"></script> + <script src="/app/directives/tc-form-fonts/tc-form-fonts.spec.js"></script> + <script src="/app/directives/tc-form-stockart/tc-form-stockart.spec.js"></script> + <script src="/app/directives/tc-input/tc-input.spec.js"></script> <script src="/app/directives/tc-paginator/tc-paginator.spec.js"></script> <script src="/app/directives/tc-tabs/tc-tabs.directive.spec.js"></script> <script src="/app/my-dashboard/community-updates/community-updates.spec.js"></script> <script src="/app/my-dashboard/header-dashboard/header-dashboard.spec.js"></script> <script src="/app/my-dashboard/my-challenges/my-challenges.spec.js"></script> <script src="/app/my-dashboard/programs/programs.spec.js"></script> - <script src="/app/my-dashboard/srms/srms.spec.js"></script> <script src="/app/my-dashboard/subtrack-stats/subtrack-stats.controller.spec.js"></script> + <script src="/app/my-dashboard/srms/srms.spec.js"></script> <script src="/app/peer-review/completed-review/completed-review.spec.js"></script> <script src="/app/peer-review/edit-review/edit-review.spec.js"></script> <script src="/app/peer-review/readOnlyScorecard/readOnlyScorecard.spec.js"></script> @@ -291,6 +321,7 @@ <h1><a href="specs.html">Spec Runner</a></h1> <script src="/app/settings/account-info/account-info.spec.js"></script> <script src="/app/settings/edit-profile/edit-profile.spec.js"></script> <script src="/app/settings/preferences/preferences.spec.js"></script> + <script src="/app/submissions/submit-design-files/submit-design-files.spec.js"></script> <script src="/app/directives/account/toggle-password/toggle-password.spec.js"></script> <script src="/app/directives/account/toggle-password-with-tips/toggle-password-with-tips.spec.js"></script> <!-- endinject --> diff --git a/app/submissions/submission-error/submission-error.jade b/app/submissions/submission-error/submission-error.jade new file mode 100644 index 000000000..d904ccd94 --- /dev/null +++ b/app/submissions/submission-error/submission-error.jade @@ -0,0 +1,3 @@ +.panel-body + p.tc-error-messages.submissions-access-error(ng-bind="submissions.errorMessage") + diff --git a/app/submissions/submissions.controller.js b/app/submissions/submissions.controller.js index 63c24e445..3f12e51cf 100644 --- a/app/submissions/submissions.controller.js +++ b/app/submissions/submissions.controller.js @@ -3,18 +3,34 @@ angular.module('tc.submissions').controller('SubmissionsController', SubmissionsController); - SubmissionsController.$inject = ['challengeToSubmitTo']; + SubmissionsController.$inject = ['challengeToSubmitTo', '$state']; - function SubmissionsController(challengeToSubmitTo) { + function SubmissionsController(challengeToSubmitTo, $state) { var vm = this; - var challenge = challengeToSubmitTo.challenge; - vm.challengeTitle = challenge.name; - vm.challengeId = challenge.id; - vm.track = challenge.track.toLowerCase(); + vm.error = !!challengeToSubmitTo.error; - activate(); + if (vm.error) { + vm.errorType = challengeToSubmitTo.error.type; + vm.errorMessage = challengeToSubmitTo.error.message; + vm.challengeError = vm.errorType === 'challenge'; + } - function activate() {} + if (challengeToSubmitTo.challenge) { + var challenge = challengeToSubmitTo.challenge; + vm.challengeTitle = challenge.name; + vm.challengeId = challenge.id; + vm.track = challenge.track.toLowerCase(); + + if (challengeToSubmitTo.error) { + $state.go('submissions.file.error'); + } else { + if (challenge.track === 'DESIGN') { + $state.go('submissions.file.design'); + } else if (challenge.track === 'DEVELOP') { + $state.go('submissions.file.develop') + } + } + } } })(); diff --git a/app/submissions/submissions.jade b/app/submissions/submissions.jade index 309269929..85ea902e0 100644 --- a/app/submissions/submissions.jade +++ b/app/submissions/submissions.jade @@ -1,5 +1,5 @@ .panel-page - .panel-header.flex.space-between + .panel-header.flex.space-between(ng-if="!submissions.challengeError") a.panel-header__back-button.flex.space-between.middle(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") //- TODO: Replace below with svg tag diff --git a/app/submissions/submissions.routes.js b/app/submissions/submissions.routes.js index a80987887..d6683bf91 100644 --- a/app/submissions/submissions.routes.js +++ b/app/submissions/submissions.routes.js @@ -18,9 +18,7 @@ controllerAs: 'submissions', data: { authRequired: true, - - // TODO: Get title from PMs - title: 'Submit' + title: 'Challenge Submission' }, resolve: { challengeToSubmitTo: ['ChallengeService', '$stateParams', 'UserService', function(ChallengeService, $stateParams, UserService) { @@ -31,15 +29,19 @@ var userHandle = UserService.getUserIdentity().handle; + var error = null; + return ChallengeService.getUserChallenges(userHandle, params) .then(function(challenge) { - challenge = challenge[0]; + challenge = challenge[0].plain(); if (!challenge) { - // There should be a challenge, redirect? - alert('User is not associated with this challenge.'); + setErrorMessage('challenge', 'This is not a valid challenge. Use your browser\'s back button to return.'); + return { + error: error, + challenge: null + }; } - var phaseType; var phaseId; @@ -60,33 +62,65 @@ return false; }); + if (!isPhaseSubmission) { + setErrorMessage('phase', 'Submission phases are not currently open for this challenge.') + } + var isSubmitter = _.some(challenge.userDetails.roles, function(role) { return role === 'Submitter'; }); - if (!isPhaseSubmission || !isSubmitter) { - // TODO: Where do we redirect if you can't submit? - alert('You should not have access to this page'); + if (!isSubmitter) { + setErrorMessage('submitter', 'You do not have a submitter role for this challenge.') } return { + error: error, challenge: challenge, phaseType: phaseType, phaseId: phaseId }; }) .catch(function(err) { - console.log('ERROR GETTING CHALLENGE: ', err); - alert('There was an error accessing this page'); - // TODO: Where do we redirect if there is an error? + setErrorMessage('challenge', 'There was an error getting information for this challenge.'); + + return { + error: error, + challenge: null + }; }); + + function setErrorMessage(type, message) { + // Sets the error as the first error encountered + if (!error) { + error = { + type: type, + message: message + }; + } + } }] } }, 'submissions.file': { - url: '?method=file', - templateUrl: 'submissions/submit-file/submit-file.html', - controller: 'SubmitFileController', + url:'file/', + abstract: true, + template: '<ui-view/>' + }, + 'submissions.file.error': { + url: '', + templateUrl: 'submissions/submission-error/submission-error.html', + }, + 'submissions.file.design': { + url:'', + templateUrl: 'submissions/submit-design-files/submit-design-files.html', + controller: 'SubmitDesignFilesController', + controllerAs: 'vm', + }, + 'submissions.file.develop': { + url:'', + templateUrl: 'submissions/submit-develop-files/submit-develop-files.html', + controller: 'SubmitDevelopFilesController', controllerAs: 'vm', } }; diff --git a/app/submissions/submissions.spec.js b/app/submissions/submissions.spec.js index 983dd7669..91ae5b4b9 100644 --- a/app/submissions/submissions.spec.js +++ b/app/submissions/submissions.spec.js @@ -1,7 +1,6 @@ /* jshint -W117, -W030 */ describe('Submissions Controller', function() { - var controller; - var vm; + var controller, vm; var mockChallenge = { challenge: { @@ -11,6 +10,10 @@ describe('Submissions Controller', function() { } }; + var state = { + go: sinon.spy() + } + beforeEach(function() { bard.appModule('tc.submissions'); bard.inject(this, '$controller'); @@ -20,12 +23,81 @@ describe('Submissions Controller', function() { beforeEach(function() { controller = $controller('SubmissionsController', { - challengeToSubmitTo: mockChallenge + challengeToSubmitTo: mockChallenge, + $state: state }); vm = controller; }); - it('should exist', function() { + it('exists', function() { expect(vm).to.exist; }); + + it('sets error properties when there is an error passed down', function() { + controller = $controller('SubmissionsController', { + challengeToSubmitTo: { + challenge: null, + error: { + type: 'challenge', + message: 'error getting challenge information' + } + }, + $state: state + }); + vm = controller; + + expect(vm.errorType).to.equal('challenge'); + expect(vm.errorMessage).to.equal('error getting challenge information'); + expect(vm.challengeError).to.be.true; + }); + + it('sets challenge properties when there is a challenge from the routes resolve', function() { + expect(vm.challengeTitle).to.equal(mockChallenge.challenge.name); + expect(vm.challengeId).to.equal(30049240); + expect(vm.track).to.equal(mockChallenge.challenge.track.toLowerCase()); + }); + + + describe('routes to the correct child state for', function() { + it('design challenges', function() { + + expect(state.go).calledWith('submissions.file.design'); + }); + + it('develop challenges', function() { + controller = $controller('SubmissionsController', { + challengeToSubmitTo: { + challenge: { + name: 'Challenge Name', + track: 'DEVELOP', + id: 30049240 + } + }, + $state: state + }); + vm = controller; + + expect(state.go).calledWith('submissions.file.develop'); + }); + + it('errors', function() { + controller = $controller('SubmissionsController', { + challengeToSubmitTo: { + challenge: { + name: 'Challenge Name', + track: 'DEVELOP', + id: 30049240 + }, + error: { + type: 'phase', + message: 'No open submissions phase' + } + }, + $state: state + }); + vm = controller; + + expect(state.go).calledWith('submissions.file.error'); + }); + }); }); diff --git a/app/submissions/submit-file/submit-file.controller.js b/app/submissions/submit-design-files/submit-design-files.controller.js similarity index 72% rename from app/submissions/submit-file/submit-file.controller.js rename to app/submissions/submit-design-files/submit-design-files.controller.js index 11dfbd604..6b8c9b70e 100644 --- a/app/submissions/submit-file/submit-file.controller.js +++ b/app/submissions/submit-design-files/submit-design-files.controller.js @@ -1,13 +1,13 @@ (function () { 'use strict'; - angular.module('tc.submissions').controller('SubmitFileController', SubmitFileController); + angular.module('tc.submissions').controller('SubmitDesignFilesController', SubmitDesignFilesController); - SubmitFileController.$inject = ['$scope', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo']; + SubmitDesignFilesController.$inject = ['$scope','$window', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo']; - function SubmitFileController($scope, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) { + function SubmitDesignFilesController($scope, $window, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) { var vm = this; - $log = $log.getInstance('SubmitFileController'); + $log = $log.getInstance('SubmitDesignFilesController'); var files = {}; var fileUploadProgress = {}; vm.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/); @@ -19,30 +19,8 @@ vm.finishing = false; vm.showProgress = false; vm.errorInUpload = false; - vm.formFonts = { - 0: { - id: 0, - source: '', - name: '', - sourceUrl: '', - isFontUrlRequired: false, - isFontUrlDisabled: true, - isFontNameRequired: false, - isFontNameDisabled: true, - isFontSourceRequired: false - } - }; - vm.formStockarts = { - 0: { - id: 1, - description: '', - sourceUrl: '', - fileNumber: '', - isPhotoDescriptionRequired: false, - isPhotoURLRequired: false, - isFileNumberRequired: false - } - }; + vm.formFonts = {}; + vm.formStockarts = {}; vm.submissionForm = { files: [], @@ -61,8 +39,6 @@ vm.submissionsBody = { reference: { - - // type dynamic or static? type: 'CHALLENGE', id: $stateParams.challengeId, phaseType: challengeToSubmitTo.phaseType, @@ -70,9 +46,7 @@ }, userId: userId, data: { - - // Dynamic or static? - method: 'DESIGN_CHALLENGE_ZIP_FILE', + method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE', // Can delete below since they are processed and added later? files: [], @@ -86,6 +60,7 @@ vm.setRankTo1 = setRankTo1; vm.setFileReference = setFileReference; vm.uploadSubmission = uploadSubmission; + vm.refreshPage = refreshPage; vm.cancelRetry = cancelRetry; activate(); @@ -121,17 +96,20 @@ fileObject.mediaType = file.type; } - // If user picks a new file, replace the that file's fileObject with a new one - // Or add it the list if it's not there - if (vm.submissionsBody.data.files.length) { - vm.submissionsBody.data.files.some(function(file, i, filesArray) { - if (file.type === fileObject.type) { - file = fileObject; - } else if (filesArray.length === i + 1) { - filesArray.push(fileObject); - } - }); - } else { + // If user changes a file input's file, update the file details + var isFound = vm.submissionsBody.data.files.reduce(function(isFound, file, i, filesArray) { + if (isFound) { return true; } + + if (file.type === fileObject.type) { + filesArray[i] = fileObject; + return true; + } + + return false; + }, false); + + // Add new files to the list + if (!isFound) { vm.submissionsBody.data.files.push(fileObject); } } @@ -148,10 +126,18 @@ vm.submissionsBody.data.submitterRank = vm.submissionForm.submitterRank; // Process stock art - var processedStockarts = _.map(vm.formStockarts, function(formStockart) { + var processedStockarts = _.reduce(vm.formStockarts, function(compiledStockarts, formStockart) { + if (formStockart.description) { delete formStockart.id; - return formStockart; - }); + delete formStockart.isPhotoDescriptionRequired; + delete formStockart.isPhotoURLRequired; + delete formStockart.isFileNumberRequired; + + compiledStockarts.push(formStockart); + } + + return compiledStockarts; + }, []); vm.submissionsBody.data.stockArts = processedStockarts; @@ -164,6 +150,7 @@ delete formFont.isFontNameRequired; delete formFont.isFontNameDisabled; delete formFont.isFontSourceRequired; + compiledFonts.push(formFont); } @@ -176,10 +163,8 @@ SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress); } - /* - * Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH - * of the submission upload and updates the progress UI accordingly. - */ + // Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH + // of the submission upload and updates the progress UI accordingly. function updateProgress(phase, args) { // for PREPARE phase if (phase === 'PREPARE') { @@ -204,11 +189,13 @@ count++; } vm.uploadProgress = total / count; + // initiate digest cycle because this event (xhr event) is caused outside angular $scope.$apply(); } else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase vm.uploadProgress = args; } + // start next phase when UPLOAD is done if (vm.uploadProgress == 100) { $log.debug('Uploaded files.'); @@ -219,20 +206,20 @@ // we are concerned only for completion of the phase if (args === 100) { $log.debug('Finished upload.'); - vm.finishing = false; - vm.showProgress = false; - - // TODO redirect to submission listing / challenge details page } - } else { // assume it to be error condition + } else { + // assume it to be error condition $log.debug("Error Condition: " + phase); vm.errorInUpload = true; } } + function refreshPage() { + $window.location.reload(true); + } + function cancelRetry() { vm.showProgress = false; - // TODO redirect to submission listing / challenge details page } } })(); diff --git a/app/submissions/submit-file/submit-file.jade b/app/submissions/submit-design-files/submit-design-files.jade similarity index 88% rename from app/submissions/submit-file/submit-file.jade rename to app/submissions/submit-design-files/submit-design-files.jade index 0dc8534f4..4225636ec 100644 --- a/app/submissions/submit-file/submit-file.jade +++ b/app/submissions/submit-design-files/submit-design-files.jade @@ -10,7 +10,7 @@ .panel-body form.form-blocks(name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate) - .form-block.flex.wrap + .form-block.flex .form-block__instructions .form-block__title Files @@ -96,7 +96,7 @@ .tc-error-messages(ng-show="submissionForm.Submission_Rank.$dirty && submissionForm.Submission_Rank.$invalid") p(ng-show="submissionForm.Submission_Rank.$error.pattern") Please enter a positive integer. - .form-block.flex.wrap + .form-block.flex .form-block__instructions .form-block__title Notes @@ -104,15 +104,16 @@ p Type a short note about your design here. Explain revisions or other design elements that may not be clear. .form-block__fields - tc-textarea.tc-textarea( - label-text="Comments", - placeholder="My design tries to solve the problem with a particular idea in mind. The use of color is based on the provided brand guideline. The flows are included in the sub folder. I followed all revisions as per the directions provided.", - character-count="true", - character-count-max="500", - value="vm.comments" - ) - - .form-block.flex.wrap + .fieldset + tc-textarea.tc-textarea( + label-text="Comments", + placeholder="My design tries to solve the problem with a particular idea in mind. The use of color is based on the provided brand guideline.", + character-count="true", + character-count-max="500", + value="vm.comments" + ) + + .form-block.flex .form-block__instructions .form-block__title Did you use custom fonts? @@ -127,7 +128,7 @@ .fieldsets tc-form-fonts(form-fonts="vm.formFonts") - .form-block.flex.wrap + .form-block.flex .form-block__instructions .form-block__title Did you use stock art? @@ -141,7 +142,7 @@ .panel-footer p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use). - p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. Fix them before you can upload your submission. + p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. You must fix them before you can upload your submission. .checkbox.flex.center input(type="checkbox", ng-model="vm.submissionForm.hasAgreedToTerms", id="agree-to-terms", required) @@ -150,7 +151,7 @@ button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit -modal.transition(show="vm.showProgress", background-click-close="true", style="background-color:white;") +modal.transition(show="vm.showProgress", background-click-close="false", style="background-color:white;") .upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}") .upload-progress__title p Uploading submission for @@ -158,17 +159,24 @@ modal.transition(show="vm.showProgress", background-click-close="true", style="b p.upload-progress-title__challenge-name [Challenge name] img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload") - img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload") - p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working, you’ll loose all files! + p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working or you’ll loose all files! - p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! The file couldn’t be uploaded, I’m so sorry. + p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! One of the files couldn’t be uploaded, I’m so sorry. progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded") .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...] - .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") #[span Finishing...] + .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") + p Finished! + + .upload-progess__links + a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to the challenge + + a.tc-btn.tc-btn-s.tc-btn-ghost(ng-click="vm.refreshPage()") Submit another + + .upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed] .upload-progress__error-action(ng-show="vm.errorInUpload") diff --git a/app/submissions/submit-design-files/submit-design-files.spec.js b/app/submissions/submit-design-files/submit-design-files.spec.js new file mode 100644 index 000000000..13898bd86 --- /dev/null +++ b/app/submissions/submit-design-files/submit-design-files.spec.js @@ -0,0 +1,318 @@ +/* jshint -W117, -W030 */ +describe('Submit Design Files Controller', function() { + var controller, vm, scope; + + var mockChallenge = { + challenge: { + name: 'Challenge Name', + track: 'DESIGN', + id: 30049240 + } + }; + + var userService = { + getUserIdentity: function() { + return { + userId: 123456 + }; + } + }; + + var submissionsService = { + getPresignedURL: function() {} + }; + + var mockWindow = { + location: { + reload: function(val) { return val; } + } + }; + + beforeEach(function() { + bard.appModule('tc.submissions'); + bard.inject(this, '$controller', '$rootScope'); + + scope = $rootScope.$new(); + }); + + bard.verifyNoOutstandingHttpRequests(); + + beforeEach(function() { + controller = $controller('SubmitDesignFilesController', { + $scope: scope, + UserService: userService, + challengeToSubmitTo: mockChallenge, + SubmissionsService: submissionsService, + $window: mockWindow + }); + vm = controller; + }); + + it('exists', function() { + expect(vm).to.exist; + }); + + it('sets the right track for the method', function() { + controller = $controller('SubmitDesignFilesController', { + $scope: scope, + UserService: userService, + challengeToSubmitTo: { + challenge: { + name: 'Challenge Name', + track: 'DEVELOP', + id: 30049240 + } + }, + SubmissionsService: submissionsService, + $window: mockWindow + }); + vm = controller; + scope.$digest(); + + expect(vm.submissionsBody.data.method).to.equal('DEVELOP_CHALLENGE_ZIP_FILE'); + }); + + describe('setRankTo1', function() { + it('returns 1 if the input is blank', function() { + expect(vm.setRankTo1('')).to.equal(1); + }); + + it('returns the input value if not blank', function() { + var inputText = 'sample input text'; + var result = vm.setRankTo1(inputText); + + expect(result).to.equal(inputText); + }); + }); + + + describe('setFileReference', function() { + var file, fieldId; + + beforeEach(function() { + file = { + name: 'Dashboard 2.png', + size: 575548, + type: 'image/png' + }; + fieldId = 'DESIGN_COVER'; + + vm.setFileReference(file, fieldId); + scope.$digest(); + }); + + afterEach(function() { + file = undefined; + fieldId = undefined; + }); + + it('adds a file object to the submissions body', function() { + expect(vm.submissionsBody.data.files).to.have.length(1); + }); + + it('replaces a file object with a new one if it has the same fieldId', function() { + expect(vm.submissionsBody.data.files).to.have.length(1); + + var newFile = { + name: 'different_image.png', + size: 4321, + type: 'image/png' + }; + + vm.setFileReference(newFile, fieldId); + scope.$digest(); + + expect(vm.submissionsBody.data.files).to.have.length(1); + expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png'); + }); + + it('sets the correct mediaTypes on the fileObject', function() { + expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png'); + + var newFile = { + name: 'submission.zip', + size: 43121, + type: 'application/zip' + }; + var newFieldId = 'SUBMISSION_ZIP'; + + vm.setFileReference(newFile, newFieldId); + scope.$digest(); + + expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream'); + + var newFile2 = { + name: 'source.zip', + size: 2314, + type: 'application/zip' + }; + var newFieldId2 = 'SOURCE_ZIP'; + + vm.setFileReference(newFile2, newFieldId2); + scope.$digest(); + + expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream'); + }); + }); + + describe('uploadSubmission', function() { + it('adds comments to the submissions body', function() { + vm.comments = 'test comments'; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + + expect(vm.submissionsBody.data.submitterComments).to.equal('test comments'); + }); + + it('adds the rank to the submissions body', function() { + vm.submissionForm.submitterRank = 3; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + + expect(vm.submissionsBody.data.submitterRank).to.equal(3); + }); + + it('calls the submission service', function() { + var mockAPICall = sinon.spy(submissionsService, 'getPresignedURL'); + + vm.uploadSubmission(); + scope.$digest(); + + expect(mockAPICall).calledOnce; + }); + + describe('processes the stockart and', function() { + it('returns an empty array if no stockart given', function() { + vm.formStockarts = []; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + + expect(vm.submissionsBody.data.stockArts).to.deep.equal([]); + }); + + it('removes the required properties and id from each stockart', function() { + vm.formStockarts = [ + { + id: 0, + description: 'first stockart', + sourceUrl: 'url.com', + fileNumber: '123', + isPhotoDescriptionRequired: false, + isPhotoURLRequired: false, + isFileNumberRequired: false + }, + { + id: 1, + description: 'second stockart', + sourceUrl: 'url2.com', + fileNumber: '234', + isPhotoDescriptionRequired: false, + isPhotoURLRequired: false, + isFileNumberRequired: false + } + ]; + var processedStockart = [ + { + description: 'first stockart', + sourceUrl: 'url.com', + fileNumber: '123', + }, + { + description: 'second stockart', + sourceUrl: 'url2.com', + fileNumber: '234', + } + ]; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + expect(vm.submissionsBody.data.stockArts).to.deep.equal(processedStockart); + + }); + }); + describe('processes the fonts and', function() { + it('returns an empty array if no fonts given', function() { + vm.formFonts = []; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + + expect(vm.submissionsBody.data.fonts).to.deep.equal([]); + }); + + it('removes the required properties and id from each font', function() { + vm.formFonts = [ + { + id: 0, + source: 'STUDIO_STANDARD_FONTS_LIST', + name: 'my font', + isFontUrlRequired: false, + isFontUrlDisabled: true, + isFontNameRequired: false, + isFontNameDisabled: true, + isFontSourceRequired: false + }, + { + id: 1, + source: 'FONTS_DOT_COM', + name: 'my other font', + sourceUrl: 'fontsource.com', + isFontUrlRequired: false, + isFontUrlDisabled: true, + isFontNameRequired: false, + isFontNameDisabled: true, + isFontSourceRequired: false + } + ]; + var processedFonts = [ + { + source: 'STUDIO_STANDARD_FONTS_LIST', + name: 'my font', + }, + { + source: 'FONTS_DOT_COM', + name: 'my other font', + sourceUrl: 'fontsource.com', + } + ]; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + expect(vm.submissionsBody.data.fonts).to.deep.equal(processedFonts); + }); + }); + }); + + describe('refreshPage', function() { + it('reloads the page', function() { + var mockRefresh = sinon.spy(mockWindow.location, 'reload'); + + vm.refreshPage(); + scope.$digest(); + + expect(mockRefresh).calledWith(true); + expect(mockRefresh).calledOnce; + }); + }); + + describe('cancelRetry', function() { + it('sets showProgress to false', function() { + vm.showProgress = true; + scope.$digest(); + expect(vm.showProgress).to.be.true; + + vm.cancelRetry(); + scope.$digest(); + expect(vm.showProgress).to.be.false; + }); + }); +}); diff --git a/app/submissions/submit-develop-files/submit-develop-files.controller.js b/app/submissions/submit-develop-files/submit-develop-files.controller.js new file mode 100644 index 000000000..9b40e7fc4 --- /dev/null +++ b/app/submissions/submit-develop-files/submit-develop-files.controller.js @@ -0,0 +1,171 @@ +(function () { + 'use strict'; + + angular.module('tc.submissions').controller('SubmitDevelopFilesController', SubmitDevelopFilesController); + + SubmitDevelopFilesController.$inject = ['$scope','$window', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo']; + + function SubmitDevelopFilesController($scope, $window, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) { + var vm = this; + $log = $log.getInstance('SubmitDevelopFilesController'); + var files = {}; + var fileUploadProgress = {}; + vm.comments = ''; + vm.uploadProgress = 0; + vm.uploading = false; + vm.preparing = false; + vm.finishing = false; + vm.showProgress = false; + vm.errorInUpload = false; + vm.submissionForm = { + files: [], + + // use develop name + sourceZip: null, + + submitterComments: '', + hasAgreedToTerms: false + }; + + var userId = parseInt(UserService.getUserIdentity().userId); + + vm.submissionsBody = { + reference: { + type: 'CHALLENGE', + id: $stateParams.challengeId, + phaseType: challengeToSubmitTo.phaseType, + phaseId: challengeToSubmitTo.phaseId + }, + userId: userId, + data: { + method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE', + + // Can delete below since they are processed and added later? + files: [], + submitterComments: '', + } + }; + + vm.setFileReference = setFileReference; + vm.uploadSubmission = uploadSubmission; + vm.refreshPage = refreshPage; + vm.cancelRetry = cancelRetry; + + activate(); + + function activate() {} + + function setFileReference(file, fieldId) { + // Can clean up since fileValue on tcFileInput has file reference? + files[fieldId] = file; + + var fileObject = { + name: file.name, + type: fieldId, + status: 'PENDING' + }; + + // TODO: Refactor or develop + switch(fieldId) { + case 'SUBMISSION_ZIP': + fileObject.mediaType = 'application/octet-stream'; + break; + case 'SOURCE_ZIP': + fileObject.mediaType = 'application/octet-stream'; + break; + default: + fileObject.mediaType = file.type; + } + + // If user changes a file input's file, update the file details + var isFound = vm.submissionsBody.data.files.reduce(function(isFound, file, i, filesArray) { + if (isFound) { return true; } + + if (file.type === fileObject.type) { + filesArray[i] = fileObject; + return true; + } + + return false; + }, false); + + // Add new files to the list + if (!isFound) { + vm.submissionsBody.data.files.push(fileObject); + } + } + + function uploadSubmission() { + vm.errorInUpload = false; + vm.uploadProgress = 0; + vm.fileUploadProgress = {}; + vm.showProgress = true; + vm.preparing = true; + vm.uploading = false; + vm.finishing = false; + vm.submissionsBody.data.submitterComments = vm.comments; + + $log.debug('Body for request: ', vm.submissionsBody); + SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress); + } + + // Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH + // of the submission upload and updates the progress UI accordingly. + function updateProgress(phase, args) { + // for PREPARE phase + if (phase === 'PREPARE') { + // we are concerned only for completion of the phase + if (args === 100) { + vm.preparing = false; + vm.uploading = true; + $log.debug('Prepared for upload.'); + } + } else if (phase === 'UPLOAD') { + // if args is object, this update is about XHRRequest's upload progress + if (typeof args === 'object') { + var requestId = args.file; + var progress = args.progress; + if (!fileUploadProgress[requestId] || fileUploadProgress[requestId] < progress) { + fileUploadProgress[requestId] = progress; + } + var total = 0, count = 0; + for(var requestId in fileUploadProgress) { + var prog = fileUploadProgress[requestId]; + total += prog; + count++; + } + vm.uploadProgress = total / count; + + // initiate digest cycle because this event (xhr event) is caused outside angular + $scope.$apply(); + } else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase + vm.uploadProgress = args; + } + + // start next phase when UPLOAD is done + if (vm.uploadProgress == 100) { + $log.debug('Uploaded files.'); + vm.uploading = false; + vm.finishing = true; + } + } else if (phase === 'FINISH') { + // we are concerned only for completion of the phase + if (args === 100) { + $log.debug('Finished upload.'); + } + } else { + // assume it to be error condition + $log.debug("Error Condition: " + phase); + vm.errorInUpload = true; + } + } + + function refreshPage() { + $window.location.reload(true); + } + + function cancelRetry() { + vm.showProgress = false; + } + } +})(); diff --git a/app/submissions/submit-develop-files/submit-develop-files.jade b/app/submissions/submit-develop-files/submit-develop-files.jade new file mode 100644 index 000000000..9d2241fd5 --- /dev/null +++ b/app/submissions/submit-develop-files/submit-develop-files.jade @@ -0,0 +1,90 @@ +.mobile-redirect + .mobile-redirect__title File upload is not available for mobile + + .mobile-redirect__body + p Our team is working hard on new features, and file upload currently only works on the web. Please open this page on your desktop if you want to create a submission. + + a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") TODO: same link as below for help on formatting submission + + a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to Challenge Details + +.panel-body + form.form-blocks(name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate) + .form-block.flex + .form-block__instructions + .form-block__title Files + + .form-block__text + p Need text from PMs. + + p Need text from PMs. + + p Need text from PMs. + + a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") Need link from PMs + + .form-block__fields + .fieldset + tc-file-input.tc-file-field( + label-text="Preview Image", + field-id="DESIGN_COVER", + button-text="Add File", + file-type="jpg,jpeg,png" + placeholder="Image file as .jpg or .png", + mandatory="true", + set-file-reference="vm.setFileReference(file, fieldId)", + ng-model="vm.submissionForm.designCover" + ) + + .tc-error-messages( + ng-show="submissionForm['DESIGN_COVER'].$touched && submissionForm['DESIGN_COVER'].$invalid", + ng-messages="submissionForm['DESIGN_COVER'].$error" + ) + p(ng-message="filesize") File size may not exceed 500MB. + + p(ng-message="required") This is not the correct file format. Please select a .jpg or .png file. + + .panel-footer + p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use). + + p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. You must fix them before you can upload your submission. + + .checkbox.flex.center + input(type="checkbox", ng-model="vm.submissionForm.hasAgreedToTerms", id="agree-to-terms", required) + + label(for="agree-to-terms") I understand and agree + + button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit + +modal.transition(show="vm.showProgress", background-click-close="false", style="background-color:white;") + .upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}") + .upload-progress__title + p Uploading submission for + + p.upload-progress-title__challenge-name [Challenge name] + + img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload") + img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload") + + p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working or you’ll loose all files! + + p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! One of the files couldn’t be uploaded, I’m so sorry. + + progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded") + + .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...] + .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") + p Finished! + + .upload-progess__links + a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to the challenge + + a.tc-btn.tc-btn-s.tc-btn-ghost(ng-click="vm.refreshPage()") Submit another + + + .upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed] + + .upload-progress__error-action(ng-show="vm.errorInUpload") + button.tc-btn.tc-btn-s.tc-btn-ghost(type="button", ng-click="vm.cancelRetry()") Cancel + + button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again diff --git a/app/submissions/submit-develop-files/submit-develop-files.spec.js b/app/submissions/submit-develop-files/submit-develop-files.spec.js new file mode 100644 index 000000000..97ca01833 --- /dev/null +++ b/app/submissions/submit-develop-files/submit-develop-files.spec.js @@ -0,0 +1,191 @@ +/* jshint -W117, -W030 */ +describe('Submit Develop Files Controller', function() { + var controller, vm, scope; + + var mockChallenge = { + challenge: { + name: 'Challenge Name', + track: 'DEVELOP', + id: 30049240 + } + }; + + var userService = { + getUserIdentity: function() { + return { + userId: 123456 + }; + } + }; + + var submissionsService = { + getPresignedURL: function() {} + }; + + var mockWindow = { + location: { + reload: function(val) { return val; } + } + }; + + beforeEach(function() { + bard.appModule('tc.submissions'); + bard.inject(this, '$controller', '$rootScope'); + + scope = $rootScope.$new(); + }); + + bard.verifyNoOutstandingHttpRequests(); + + beforeEach(function() { + controller = $controller('SubmitDevelopFilesController', { + $scope: scope, + UserService: userService, + challengeToSubmitTo: mockChallenge, + SubmissionsService: submissionsService, + $window: mockWindow + }); + vm = controller; + }); + + it('exists', function() { + expect(vm).to.exist; + }); + + it('sets the right track for the method', function() { + controller = $controller('SubmitDevelopFilesController', { + $scope: scope, + UserService: userService, + challengeToSubmitTo: { + challenge: { + name: 'Challenge Name', + track: 'DESIGN', + id: 30049240 + } + }, + SubmissionsService: submissionsService, + $window: mockWindow + }); + vm = controller; + scope.$digest(); + + expect(vm.submissionsBody.data.method).to.equal('DESIGN_CHALLENGE_ZIP_FILE'); + }); + + describe('setFileReference', function() { + var file, fieldId; + + beforeEach(function() { + // TODO: change to be more relevant to develop + file = { + name: 'Dashboard 2.png', + size: 575548, + type: 'image/png' + }; + fieldId = 'DESIGN_COVER'; + + vm.setFileReference(file, fieldId); + scope.$digest(); + }); + + afterEach(function() { + file = undefined; + fieldId = undefined; + }); + + it('adds a file object to the submissions body', function() { + expect(vm.submissionsBody.data.files).to.have.length(1); + }); + + it('replaces a file object with a new one if it has the same fieldId', function() { + expect(vm.submissionsBody.data.files).to.have.length(1); + + // TODO: change to be more relevant to develop submissions + var newFile = { + name: 'different_image.png', + size: 4321, + type: 'image/png' + }; + + vm.setFileReference(newFile, fieldId); + scope.$digest(); + + expect(vm.submissionsBody.data.files).to.have.length(1); + expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png'); + }); + + it('sets the correct mediaTypes on the fileObject', function() { + // TODO: change to be more relevant to develop + expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png'); + + var newFile = { + name: 'submission.zip', + size: 43121, + type: 'application/zip' + }; + var newFieldId = 'SUBMISSION_ZIP'; + + vm.setFileReference(newFile, newFieldId); + scope.$digest(); + + expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream'); + + var newFile2 = { + name: 'source.zip', + size: 2314, + type: 'application/zip' + }; + var newFieldId2 = 'SOURCE_ZIP'; + + vm.setFileReference(newFile2, newFieldId2); + scope.$digest(); + + expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream'); + }); + }); + + describe('uploadSubmission', function() { + it('adds comments to the submissions body', function() { + vm.comments = 'test comments'; + scope.$digest(); + + vm.uploadSubmission(); + scope.$digest(); + + expect(vm.submissionsBody.data.submitterComments).to.equal('test comments'); + }); + + it('calls the submission service', function() { + var mockAPICall = sinon.spy(submissionsService, 'getPresignedURL'); + + vm.uploadSubmission(); + scope.$digest(); + + expect(mockAPICall).calledOnce; + }); + }); + + describe('refreshPage', function() { + it('reloads the page', function() { + var mockRefresh = sinon.spy(mockWindow.location, 'reload'); + + vm.refreshPage(); + scope.$digest(); + + expect(mockRefresh).calledWith(true); + expect(mockRefresh).calledOnce; + }); + }); + + describe('cancelRetry', function() { + it('sets showProgress to false', function() { + vm.showProgress = true; + scope.$digest(); + expect(vm.showProgress).to.be.true; + + vm.cancelRetry(); + scope.$digest(); + expect(vm.showProgress).to.be.false; + }); + }); +}); diff --git a/app/submissions/submit-file/submit-file.spec.js b/app/submissions/submit-file/submit-file.spec.js deleted file mode 100644 index d24961aca..000000000 --- a/app/submissions/submit-file/submit-file.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -/* jshint -W117, -W030 */ -describe('Submit File Controller', function() { - var controller; - var vm; - var scope; - - var mockChallenge = { - challenge: { - name: 'Challenge Name', - track: 'DESIGN', - id: 30049240 - } - }; - - userService = { - getUserIdentity: function() { - return { - userId: 123456 - }; - } - }; - - beforeEach(function() { - bard.appModule('tc.submissions'); - bard.inject(this, '$controller', '$rootScope'); - - scope = $rootScope.$new(); - }); - - bard.verifyNoOutstandingHttpRequests(); - - beforeEach(function() { - controller = $controller('SubmitFileController', { - $scope: scope, - UserService: userService, - challengeToSubmitTo: mockChallenge - }); - vm = controller; - }); - - it('should exist', function() { - expect(vm).to.exist; - }); - - describe('updateProgress ', function() { - it('should update PREPARE phase end ', function() { - - }); - }); -}); diff --git a/app/topcoder.routes.js b/app/topcoder.routes.js index e063cb6e5..2b9ca95b1 100644 --- a/app/topcoder.routes.js +++ b/app/topcoder.routes.js @@ -42,10 +42,8 @@ window.location.href = CONSTANTS.MAIN_URL + '/404/'; }] }, - /** - * Base state that all other routes should inherit from. - * Child routes can override any of the specified regions - */ + // Base state that all other routes should inherit from. + // Child routes can override any of the specified regions 'root': { url: '', abstract: true, @@ -70,10 +68,8 @@ } }, 'home': { - // TODO - set new home page parent: 'root', url: '/', - // template: 'This is the home page', controller: ['$state', function($state) { $state.go('dashboard'); }] diff --git a/assets/css/directives/challenge-links.directive.scss b/assets/css/directives/challenge-links.directive.scss index 06833d2fb..537eaf9c8 100644 --- a/assets/css/directives/challenge-links.directive.scss +++ b/assets/css/directives/challenge-links.directive.scss @@ -8,15 +8,13 @@ width: 100%; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; - padding: 0 20px; - margin-bottom: 10px; a { text-decoration: none; p { color: $accent-gray; @include font-with-weight('Sofia Pro', 500); - font-size: 12px; + font-size: 10px; line-height: 14px; text-transform: uppercase; diff --git a/assets/css/directives/challenge-tile.scss b/assets/css/directives/challenge-tile.scss index 823bb3392..de968f997 100644 --- a/assets/css/directives/challenge-tile.scss +++ b/assets/css/directives/challenge-tile.scss @@ -3,38 +3,25 @@ // Default Challenge Tile Stylings challenge-tile .challenge.tile-view { - // common css for both active and completed challenge for tile-view - height: 350px; - display: flex; - flex-direction: row; - - .challenge-track { - width: 5px; - height: 91px; - position: absolute; - top: -1px; - left: -1px; - border-top-left-radius: 4px; - } - header { width: 270px; - height: 91px; border-bottom: 1px solid #F0F0F0; display: flex; flex-direction: column; justify-content: space-between; + padding: 5px 10px; a.name { display: block; - padding: 15px 20px 0px 20px; @include font-with-weight('Sofia Pro', 500); - font-size: 14px; - line-height: 20px; + font-size: 12px; + line-height: 16px; + max-height: 48px; + overflow: hidden; color: $gray-darkest; - @include ellipsis; @include link; text-transform: uppercase; + margin-bottom: 5px; &:hover { text-decoration: none; @@ -42,27 +29,34 @@ challenge-tile .challenge.tile-view { } p.subtrack-color { - padding: 0 20px; - margin-top: 5px; @include font-with-weight('Sofia Pro', 500); - font-size: 12px; + font-size: 10px; line-height: 14px; text-transform: uppercase; + margin-bottom: 5px; } } + .challenge-card__bottom { + width: 268px;/* 2px adjustment for 2 1px borders */ + flex: 2; + display: flex; + flex-direction: column; + } + // challenge details section .challenge-details { display: flex; flex-direction: column; align-items: center; + justify-content: center; + flex: 2; } // roles bar is common for both active and completed .roles { - width: 101%; - border: 1px solid #e0e0e0; - border-top: none; + width: 100%; + border-radius: 0px 0px 4px 4px; display: flex; flex-direction: row; justify-content: flex-start; @@ -86,6 +80,10 @@ challenge-tile .challenge.tile-view { } .active-challenge { + height: 390px; + display: flex; + flex-direction: column; + justify-content: space-between; position: relative; width: 270px; border: 1px solid #E0E0E0; @@ -95,7 +93,6 @@ challenge-tile .challenge.tile-view { .challenge-details { .currentPhase { - margin-top: 40px; margin-bottom: 20px; @include font-with-weight('Sofia Pro', 300); font-size: 18px; @@ -110,7 +107,6 @@ challenge-tile .challenge.tile-view { align-items: center; width: 75px; height: 63px; - margin-bottom: 20px; background-image: url(/images/ico-calendar.svg); > p { @@ -190,6 +186,7 @@ challenge-tile .challenge.tile-view { .completed-challenge { + height: 390px; display: flex; flex-direction: column; justify-content: space-between; @@ -206,12 +203,11 @@ challenge-tile .challenge.tile-view { .date-completed { @include font-with-weight('Sofia Pro', 500); - font-size: 12px; + font-size: 10px; + line-height: 14px; text-transform: uppercase; color: $accent-gray; - padding-left: 20px; - padding-right: 20px; - margin-bottom: 10px; + margin-bottom: 5px; } .winner-ribbon { @@ -233,12 +229,35 @@ challenge-tile .challenge.tile-view { justify-content: center; } + design-challenge-user-place { + display: flex; + flex-direction: column; + flex: 2; + + .tile-view { + flex: 2; + justify-content: flex-end; + } + } + + dev-challenge-user-place { + display: flex; + flex-direction: column; + flex: 2; + + .tile-view { + flex: 2; + } + } + .marathon-score { - margin-bottom: 70px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; text-align: center; .score { - margin-top: 28px; margin-bottom: 5px; @include font-with-weight; font-size: 32px; @@ -279,16 +298,6 @@ challenge-tile .challenge.list-view { // common styles for active and completed - .challenge-track { - width: 5px; - height: 110px; - position: absolute; - top: -1px; - left: -1px; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; - } - header { display: flex; flex-direction: column; @@ -302,10 +311,11 @@ challenge-tile .challenge.list-view { a.name { display: block; @include font-with-weight('Sofia Pro', 500); - font-size: 14px; - line-height: 20px; + font-size: 12px; + line-height: 16px; + max-height: 32px; + overflow: hidden; color: $gray-darkest; - @include ellipsis; @include link; text-transform: uppercase; @@ -315,7 +325,6 @@ challenge-tile .challenge.list-view { } p.subtrack-color { - margin-bottom: 12px; @include font-with-weight('Sofia Pro', 500); font-size: 12px; line-height: 14px; @@ -449,22 +458,66 @@ challenge-tile .challenge.list-view { // Dynamic colors based on track .DESIGN { - .challenge-track { background-color: $design; } + &.tile-view { + header { + border-left: 3px solid $design; + border-radius: 3px 0 0 0; + } + } - header .subtrack-color { color: $design; } + &.challenge.list-view { + border-left: 3px solid $design; + } + + .subtrack-color { + color: $design; + } } .DEVELOP { - .challenge-track { background-color: $develop; } + &.tile-view { + header { + border-left: 3px solid $develop; + border-radius: 3px 0 0 0; + } + } - header .subtrack-color { color: $develop; } + &.challenge.list-view { + border-left: 3px solid $develop; + } + + .subtrack-color { + color: $develop; + } } .DATA_SCIENCE { - .challenge-track { background-color: $data_science; } + &.tile-view { + header { + border-left: 3px solid $data_science; + border-radius: 3px 0 0 0; + } + } - header .subtrack-color { color: $data_science; } + &.challenge.list-view { + border-left: 3px solid $data_science; + } + + .subtrack-color { + color: $data_science; + } } .COPILOT { - .challenge-track { background-color: $copilot; } + &.tile-view { + header { + border-left: 3px solid $copilot; + border-radius: 3px 0 0 0; + } + } + + &.challenge.list-view { + border-left: 3px solid $copilot; + } - header .subtrack-color { color: $copilot; } + .subtrack-color { + color: $copilot; + } } diff --git a/assets/css/directives/design-challenge-user-place.scss b/assets/css/directives/design-challenge-user-place.scss index 133a86446..540d71b80 100644 --- a/assets/css/directives/design-challenge-user-place.scss +++ b/assets/css/directives/design-challenge-user-place.scss @@ -6,15 +6,10 @@ design-challenge-user-place { .tile-view { display: flex; flex-direction: column; + justify-content: center; align-items: center; .place { - &.completed, &.passed, &.didnt { - margin-bottom: -36px; - margin-top: 52px; - } - margin-bottom: 8px; - margin-top: 8px; @include sofia-pro-regular; font-size: 20px; color: $gray-darkest; diff --git a/assets/css/directives/dev-challenge-user-place.scss b/assets/css/directives/dev-challenge-user-place.scss index b02ad190f..69f0ca8c5 100644 --- a/assets/css/directives/dev-challenge-user-place.scss +++ b/assets/css/directives/dev-challenge-user-place.scss @@ -4,8 +4,12 @@ dev-challenge-user-place { .tile-view { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + .place { - margin-bottom: 30px; @include sofia-pro-light; font-size: 18px; line-height: 23px; diff --git a/assets/css/layout/header.scss b/assets/css/layout/header.scss index 9336d0a32..10959916a 100644 --- a/assets/css/layout/header.scss +++ b/assets/css/layout/header.scss @@ -539,13 +539,8 @@ padding-right: 0; } - a { - font-weight: normal; - padding: 8px 15px; - - &:not(:last-child) { - margin-right: 12px; - } + a:not(:last-child) { + margin-right: 12px; } } } diff --git a/assets/css/my-challenges/my-challenges.scss b/assets/css/my-challenges/my-challenges.scss index b1a24e241..1e5086822 100644 --- a/assets/css/my-challenges/my-challenges.scss +++ b/assets/css/my-challenges/my-challenges.scss @@ -176,7 +176,7 @@ challenge-tile { &.tile-view { - margin-bottom: 20px; + margin-bottom: 13px; @media only screen and (max-width: 767px) { display: inline-block; margin-left: 15px; diff --git a/assets/css/submissions/submissions.scss b/assets/css/submissions/submissions.scss index b34324350..e60c2898b 100644 --- a/assets/css/submissions/submissions.scss +++ b/assets/css/submissions/submissions.scss @@ -2,6 +2,9 @@ .mobile-redirect { padding: 45px 15px 30px; + // Not showing the mobile version of the page at all + // Delete the line below to show + display: none; @media screen and (min-width: 768px) { display: none; } @@ -41,3 +44,9 @@ } } } + +.submissions-access-error { + text-align: center; + margin-left: auto; + margin-right: auto; +} diff --git a/assets/css/submissions/submit-file.scss b/assets/css/submissions/submit-file.scss index f1e801527..217e7af95 100644 --- a/assets/css/submissions/submit-file.scss +++ b/assets/css/submissions/submit-file.scss @@ -2,7 +2,15 @@ .panel-body { @media screen and (max-width: 767px) { - display: none; + // Not showing the mobile version of the page at all + // Uncomment the line below to hide the desktop page + // display: none; + } +} + +.form-blocks div:nth-last-of-type(2) { + .form-block__fields { + border-bottom: none; } } @@ -40,17 +48,72 @@ } } +.tc-error-messages { + max-width: 500px; +} + +tc-file-input { + .tc-file-field__label, .tc-file-field__inputs { + margin: 0 auto; + @media screen and (min-width: 1000px) { + margin: 0; + } + } + + & + .tc-error-messages { + max-width: 407px; + } + + .tc-label__asterisk { + color: $primary; + } +} + + .submitterRank { width: 100px; margin-top: 10px; } +.tc-textarea { + max-width: 500px; +} + +tc-form-fonts, tc-form-stockart { + width: 500px; + display: block; + margin: 0 auto; + @media screen and (min-width: 1000px) { + margin: 0; + display: inline; + } + .fieldset { + max-width: 500px; + position: relative; + } +} + +.remove-section { + position: absolute; + width: 20px; + height: 20px; + top: 15px; + right: -35px; + background-image: url(/images/x-mark-gray.svg); + background-size: 20px; + outline: 0; + @media screen and (min-width: 1000px) { + top: -5px; + } +} + +.Select { + margin-bottom: 20px; +} + modal { - .close { - .icon.cross { - background-image: url(/images/x-mark-gray.svg); - background-size: 25px; - } + > button { + display: none; } } .upload-progress { @@ -96,6 +159,7 @@ modal { font-size: 12px; line-height: 14px; color: $accent-gray-dark; + text-align: center; } .upload-progress__error { @@ -115,28 +179,14 @@ modal { } } -tc-form-fonts, tc-form-stockart { - .fieldset { - max-width: 500px; - position: relative; - } -} - -.remove-section { - position: absolute; - width: 20px; - height: 20px; - top: -5px; - right: -35px; - background-image: url(/images/x-mark-gray.svg); - background-size: 20px; - outline: 0; -} +.upload-progess__links { + margin-top: 40px; -.Select { - margin-bottom: 20px; -} + a { + display: inline-block; -.tc-error-messages { - max-width: 500px; + &:first-child { + margin-right: 10px; + } + } } diff --git a/bower.json b/bower.json index 8bedd83b4..0a55b1580 100644 --- a/bower.json +++ b/bower.json @@ -39,7 +39,7 @@ "angular-ui-router": "~0.2.15", "angular-xml": "~2.1.1", "angularjs-toaster": "~0.4.15", - "appirio-tech-ng-iso-constants": "git@github.com:appirio-tech/ng-iso-constants#~1.0.5", + "appirio-tech-ng-iso-constants": "git@github.com:appirio-tech/ng-iso-constants#~1.0.6", "appirio-tech-ng-ui-components": "appirio-tech/ng-ui-components#bower-wiredep-fix", "d3": "~3.5.6", "fontawesome": "~4.3.0",