diff --git a/common/app/routes/challenges/utils.js b/common/app/routes/challenges/utils.js index 22e9f5c962..8bcbd2c8cf 100644 --- a/common/app/routes/challenges/utils.js +++ b/common/app/routes/challenges/utils.js @@ -91,8 +91,8 @@ export function getFirstChallenge( ]; } -export function getNextChallenge(current, entites) { - const { challenge: challengeMap, block: blockMap } = entites; +export function getNextChallenge(current, entities, skip = 0) { + const { challenge: challengeMap, block: blockMap } = entities; // find current challenge // find current block // find next challenge in block @@ -102,15 +102,26 @@ export function getNextChallenge(current, entites) { } const block = blockMap[currentChallenge.block]; const index = block.challenges.indexOf(currentChallenge.dashedName); - return challengeMap[block.challenges[index + 1]]; + // use next challenge name to find challenge in challenge map + const nextChallenge = challengeMap[ + // grab next challenge name in current block + // skip is used to skip isComingSoon challenges + block.challenges[ index + 1 + skip ] + ]; + if (nextChallenge && nextChallenge.isComingSoon) { + // if we find a next challenge and it is a coming soon + // recur with plus one to skip this challenge + return getNextChallenge(current, entities, skip + 1); + } + return nextChallenge; } -export function getFirstChallengeOfNextBlock(current, entites) { +export function getFirstChallengeOfNextBlock(current, entities, skip = 0) { const { challenge: challengeMap, block: blockMap, superBlock: SuperBlockMap - } = entites; + } = entities; const currentChallenge = challengeMap[current]; if (!currentChallenge) { return null; @@ -120,24 +131,50 @@ export function getFirstChallengeOfNextBlock(current, entites) { return null; } const superBlock = SuperBlockMap[block.superBlock]; + if (!superBlock) { + return null; + } + // find index of current block const index = superBlock.blocks.indexOf(block.dashedName); - const newBlock = blockMap[superBlock.blocks[ index + 1 ]]; + + // find next block name + // and pull block object from block map + const newBlock = blockMap[ + superBlock.blocks[ index + 1 + skip ] + ]; if (!newBlock) { return null; } - return challengeMap[newBlock.challenges[0]]; + // grab first challenge from next block + const nextChallenge = challengeMap[newBlock.challenges[0]]; + if (nextChallenge && nextChallenge.isComingSoon) { + // if first challenge is coming soon, find next challenge here + const nextChallenge2 = getNextChallenge(nextChallenge.dashedName, entities); + if (!nextChallenge2) { + // whole block is coming soon + // skip this block + return getFirstChallengeOfNextBlock( + current, + entities, + skip + 1 + ); + } + return nextChallenge2; + } + return nextChallenge; } export function getFirstChallengeOfNextSuperBlock( current, - entites, - superBlocks + entities, + superBlocks, + skip = 0 ) { const { challenge: challengeMap, block: blockMap, superBlock: SuperBlockMap - } = entites; + } = entities; const currentChallenge = challengeMap[current]; if (!currentChallenge) { return null; @@ -147,13 +184,51 @@ export function getFirstChallengeOfNextSuperBlock( return null; } const superBlock = SuperBlockMap[block.superBlock]; + if (!superBlock) { + return null; + } const index = superBlocks.indexOf(superBlock.dashedName); - const newSuperBlock = SuperBlockMap[superBlocks[ index + 1]]; + const newSuperBlock = SuperBlockMap[superBlocks[ index + 1 + skip]]; if (!newSuperBlock) { return null; } - const newBlock = blockMap[newSuperBlock.blocks[0]]; - return challengeMap[newBlock.challenges[0]]; + const newBlock = blockMap[ + newSuperBlock.blocks[ 0 ] + ]; + if (!newBlock) { + return null; + } + const nextChallenge = challengeMap[newBlock.challenges[0]]; + if (!nextChallenge || !nextChallenge.isComingSoon) { + return nextChallenge; + } + // coming soon challenge, grab next + // non coming soon challenge in same block instead + const nextChallengeInBlock = getNextChallenge( + nextChallenge.dashedName, + entities + ); + if (nextChallengeInBlock) { + return nextChallengeInBlock; + } + // whole block is coming soon + // grab first challenge in next block in newSuperBlock instead + const challengeInNextBlock = getFirstChallengeOfNextBlock( + nextChallenge.dashedName, + entities + ); + + if (challengeInNextBlock) { + return challengeInNextBlock; + } + // whole super block is coming soon + // skip this super block + return getFirstChallengeOfNextSuperBlock( + current, + entities, + superBlocks, + skip + 1 + ); } export function getCurrentBlockName(current, entities) { diff --git a/common/app/routes/challenges/utils.test.js b/common/app/routes/challenges/utils.test.js new file mode 100644 index 0000000000..685c95f192 --- /dev/null +++ b/common/app/routes/challenges/utils.test.js @@ -0,0 +1,642 @@ +import test from 'tape'; +import { + getNextChallenge, + getFirstChallengeOfNextBlock, + getFirstChallengeOfNextSuperBlock +} from './utils.js'; + + +test('common/app/routes/challenges/utils', function(t) { + t.test('getNextChallenge', t => { + t.plan(4); + t.test('should return falsey when current challenge is not found', t => { + t.plan(1); + const entities = { + challenge: {}, + block: {} + }; + t.notOk( + getNextChallenge('non-existent-challenge', entities), + 'getNextChallenge did not return falsey when challenge is not found' + ); + }); + t.test('should return falsey when last challenge in block', t => { + t.plan(1); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const nextChallenge = { + dashedName: 'next-challenge', + block: 'current-block' + }; + const shouldBeNext = getNextChallenge( + 'next-challenge', + { + challenge: { + 'current-challenge': currentChallenge, + 'next-challenge': nextChallenge + }, + block: { + 'current-block': { + challenges: [ + 'current-challenge', + 'next-challenge' + ] + } + } + } + ); + t.false( + shouldBeNext, + 'getNextChallenge should return null or undefined' + ); + }); + + t.test('should return next challenge when it exists', t => { + t.plan(1); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const nextChallenge = { + dashedName: 'next-challenge', + block: 'current-block' + }; + const shouldBeNext = getNextChallenge( + 'current-challenge', + { + challenge: { + 'current-challenge': currentChallenge, + 'next-challenge': nextChallenge + }, + block: { + 'current-block': { + challenges: [ + 'current-challenge', + 'next-challenge' + ] + } + } + } + ); + t.isEqual(shouldBeNext, nextChallenge); + }); + t.test('should skip isComingSoon challenge', t => { + t.plan(1); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const comingSoon = { + dashedName: 'coming-soon', + isComingSoon: true, + block: 'current-block' + }; + const nextChallenge = { + dashedName: 'next-challenge', + block: 'current-block' + }; + const shouldBeNext = getNextChallenge( + 'current-challenge', + { + challenge: { + 'current-challenge': currentChallenge, + 'next-challenge': nextChallenge, + 'coming-soon': comingSoon, + 'coming-soon2': comingSoon + }, + block: { + 'current-block': { + challenges: [ + 'current-challenge', + 'coming-soon', + 'coming-soon2', + 'next-challenge' + ] + } + } + } + ); + t.isEqual(shouldBeNext, nextChallenge); + }); + }); + + t.test('getFirstChallengeOfNextBlock', t => { + t.plan(7); + t.test('should return falsey when current challenge is not found', t => { + t.plan(1); + const entities = { + challenge: {}, + block: {} + }; + t.notOk( + getFirstChallengeOfNextBlock('non-existent-challenge', entities), + ` + gitFirstChallengeOfNextBlock returned true value for non-existant + challenge + ` + ); + }); + t.test('should return falsey when current block is not found', t => { + t.plan(1); + const entities = { + challenge: { + 'current-challenge': { + block: 'non-existent-block' + } + }, + block: {} + }; + t.notOk( + getFirstChallengeOfNextBlock('current-challenge', entities), + ` + getFirstChallengeOfNextBlock did not returned true value block + did non exist + ` + ); + }); + t.test('should return falsey if no current superBlock found', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: { + 'current-block': { + dashedName: 'current-block', + superBlock: 'current-super-block' + } + }, + superBlock: {} + }; + t.notOk( + getFirstChallengeOfNextBlock('current-challenge', entities), + ` + getFirstChallengeOfNextBlock returned a true value + when superBlock is undefined + ` + ); + }); + t.test('should return falsey when no next block found', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: { + 'current-block': { + dashedName: 'current-block', + superBlock: 'current-super-block' + } + }, + superBlock: { + 'current-super-block': { + blocks: [ + 'current-block', + 'non-exitent-block' + ] + } + } + }; + t.notOk( + getFirstChallengeOfNextBlock('current-challenge', entities), + ` + getFirstChallengeOfNextBlock returned a value when next block + does not exist + ` + ); + }); + t.test('should return first challenge of next block', t => { + t.plan(1); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const entities = { + challenge: { + [currentChallenge.dashedName]: currentChallenge, + [firstChallenge.dashedName]: firstChallenge + }, + block: { + 'current-block': { + dashedName: 'current-block', + superBlock: 'current-super-block' + }, + 'next-block': { + dashedName: 'next-block', + superBlock: 'current-super-block', + challenges: [ 'first-challenge' ] + } + }, + superBlock: { + 'current-super-block': { + dashedName: 'current-super-block', + blocks: [ 'current-block', 'next-block' ] + } + } + }; + t.equal( + getFirstChallengeOfNextBlock(currentChallenge.dashedName, entities), + firstChallenge, + 'getFirstChallengeOfNextBlock did not return the correct challenge' + ); + }); + t.test('should skip coming soon challenge of next block', t => { + t.plan(2); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const comingSoon = { + dashedName: 'coming-soon', + block: 'next-block', + isComingSoon: true + }; + const comingSoon2 = { + dashedName: 'coming-soon2', + block: 'next-block', + isComingSoon: true + }; + const entities = { + challenge: { + [currentChallenge.dashedName]: currentChallenge, + [firstChallenge.dashedName]: firstChallenge, + 'coming-soon': comingSoon, + 'coming-soon2': comingSoon2 + }, + block: { + 'current-block': { + dashedName: 'current-block', + superBlock: 'current-super-block' + }, + 'next-block': { + dashedName: 'next-block', + superBlock: 'current-super-block', + challenges: [ + 'coming-soon', + 'coming-soon2', + 'first-challenge' + ] + } + }, + superBlock: { + 'current-super-block': { + dashedName: 'current-super-block', + blocks: [ 'current-block', 'next-block' ] + } + } + }; + t.notEqual( + getFirstChallengeOfNextBlock(currentChallenge.dashedName, entities), + comingSoon, + 'getFirstChallengeOfNextBlock returned isComingSoon challenge' + ); + t.equal( + getFirstChallengeOfNextBlock(currentChallenge.dashedName, entities), + firstChallenge, + 'getFirstChallengeOfNextBlock did not return the correct challenge' + ); + }); + t.test('should skip block if all challenges are coming soon', t => { + t.plan(2); + const currentChallenge = { + dashedName: 'current-challenge', + block: 'current-block' + }; + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const comingSoon = { + dashedName: 'coming-soon', + block: 'coming-soon-block', + isComingSoon: true + }; + const comingSoon2 = { + dashedName: 'coming-soon2', + block: 'coming-soon-block', + isComingSoon: true + }; + const entities = { + challenge: { + [currentChallenge.dashedName]: currentChallenge, + [firstChallenge.dashedName]: firstChallenge, + [comingSoon.dashedName]: comingSoon, + [comingSoon2.dashedName]: comingSoon2 + }, + block: { + 'current-block': { + dashedName: 'current-block', + superBlock: 'current-super-block' + }, + 'coming-soon-block': { + dashedName: 'coming-soon-block', + superBlock: 'current-super-block', + challenges: [ + 'coming-soon', + 'coming-soon2' + ] + }, + 'next-block': { + dashedName: 'next-block', + superBlock: 'current-super-block', + challenges: [ + 'first-challenge' + ] + } + }, + superBlock: { + 'current-super-block': { + dashedName: 'current-super-block', + blocks: [ + 'current-block', + 'coming-soon-block', + 'next-block' + ] + } + } + }; + t.notEqual( + getFirstChallengeOfNextBlock(currentChallenge.dashedName, entities), + comingSoon, + 'getFirstChallengeOfNextBlock returned isComingSoon challenge' + ); + t.equal( + getFirstChallengeOfNextBlock(currentChallenge.dashedName, entities), + firstChallenge, + 'getFirstChallengeOfNextBlock did not return the correct challenge' + ); + }); + }); + + t.test('getFirstChallengeOfNextBlock', t => { + t.plan(9); + t.test('should return falsey if current challenge not found', t => { + t.plan(1); + const entities = { + challenge: {} + }; + t.notOk( + getFirstChallengeOfNextSuperBlock('current-challenge', entities), + ); + }); + t.test('should return falsey if current block not found', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: {} + }; + t.notOk( + getFirstChallengeOfNextSuperBlock('current-challenge', entities) + ); + }); + t.test('should return falsey if current superBlock is not found', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: { 'current-block': { superBlock: 'current-super-block' } }, + superBlock: {} + }; + t.notOk( + getFirstChallengeOfNextSuperBlock('current-challenge', entities) + ); + }); + t.test('should return falsey when last superBlock', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: { 'current-block': { superBlock: 'current-super-block' } }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' } + } + }; + const superBlocks = [ 'current-super-block' ]; + t.notOk(getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + )); + }); + t.test('should return falsey when last block of new superblock', t => { + t.plan(1); + const entities = { + challenge: { 'current-challenge': { block: 'current-block' } }, + block: { + 'current-block': { + superBlock: 'current-super-block' + } + }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' }, + 'next-super-block': { + dashedName: 'next-super-block', + blocks: [ + 'first-block' + ] + } + } + }; + const superBlocks = [ 'current-super-block', 'next-super-block' ]; + t.notOk(getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + )); + }); + t.test('should return first challenge of next superBlock', t => { + t.plan(1); + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const entities = { + challenge: { + 'current-challenge': { block: 'current-block' }, + [firstChallenge.dashedName]: firstChallenge + }, + block: { + 'current-block': { superBlock: 'current-super-block' }, + 'next-block': { + superBlock: 'next-super-block', + challenges: [ 'first-challenge' ] + } + }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' }, + 'next-super-block': { + dashedName: 'next-super-block', + blocks: [ 'next-block' ] + } + } + }; + const superBlocks = [ 'current-super-block', 'next-super-block' ]; + t.isEqual( + getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + ), + firstChallenge + ); + }); + t.test('should skip coming soon challenge', t => { + t.plan(1); + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const entities = { + challenge: { + 'current-challenge': { block: 'current-block' }, + [firstChallenge.dashedName]: firstChallenge, + 'coming-soon': { + dashedName: 'coming-soon', + block: 'next-block', + isComingSoon: true + } + }, + block: { + 'current-block': { superBlock: 'current-super-block' }, + 'next-block': { + dashedName: 'next-block', + superBlock: 'next-super-block', + challenges: [ 'coming-soon', 'first-challenge' ] + } + }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' }, + 'next-super-block': { + dashedName: 'next-super-block', + blocks: [ 'next-block' ] + } + } + }; + const superBlocks = [ + 'current-super-block', + 'next-super-block' + ]; + t.isEqual( + getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + ), + firstChallenge + ); + }); + t.test('should skip coming soon block', t => { + t.plan(1); + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const entities = { + challenge: { + 'current-challenge': { block: 'current-block' }, + [firstChallenge.dashedName]: firstChallenge, + 'coming-soon': { + dashedName: 'coming-soon', + block: 'coming-soon-block', + isComingSoon: true + } + }, + block: { + 'current-block': { superBlock: 'current-super-block' }, + 'coming-soon-block': { + dashedName: 'coming-soon-block', + superBlock: 'next-super-block', + challenges: [ + 'coming-soon' + ] + }, + 'next-block': { + dashedName: 'next-block', + superBlock: 'next-super-block', + challenges: [ 'first-challenge' ] + } + }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' }, + 'next-super-block': { + dashedName: 'next-super-block', + blocks: [ 'coming-soon-block', 'next-block' ] + } + } + }; + const superBlocks = [ + 'current-super-block', + 'next-super-block' + ]; + t.isEqual( + getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + ), + firstChallenge + ); + }); + t.test('should skip coming soon super block', t => { + t.plan(1); + const firstChallenge = { + dashedName: 'first-challenge', + block: 'next-block' + }; + const entities = { + challenge: { + 'current-challenge': { block: 'current-block' }, + [firstChallenge.dashedName]: firstChallenge, + 'coming-soon': { + dashedName: 'coming-soon', + block: 'coming-soon-block', + isComingSoon: true + } + }, + block: { + 'current-block': { superBlock: 'current-super-block' }, + 'coming-soon-block': { + dashedName: 'coming-soon-block', + superBlock: 'coming-soon-super-block', + challenges: [ + 'coming-soon' + ] + }, + 'next-block': { + superBlock: 'next-super-block', + dashedName: 'next-block', + challenges: [ 'first-challenge' ] + } + }, + superBlock: { + 'current-super-block': { dashedName: 'current-super-block' }, + 'coming-soon-super-block': { + dashedName: 'coming-soon-super-block', + blocks: [ 'coming-soon-block' ] + }, + 'next-super-block': { + dashedName: 'next-super-block', + blocks: [ 'next-block' ] + } + } + }; + const superBlocks = [ + 'current-super-block', + 'coming-soon-super-block', + 'next-super-block' + ]; + t.isEqual( + getFirstChallengeOfNextSuperBlock( + 'current-challenge', + entities, + superBlocks + ), + firstChallenge + ); + }); + }); +});