diff --git a/test/content/time-travelers.test.js b/test/content/time-travelers.test.js index 1e9f5bbd94..83dc40dd38 100644 --- a/test/content/time-travelers.test.js +++ b/test/content/time-travelers.test.js @@ -6,23 +6,105 @@ import timeTravelers from '../../website/common/script/content/time-travelers'; describe('time-travelers store', () => { let user; + let date; beforeEach(() => { user = generateUser(); }); - it('removes owned sets from the time travelers store', () => { - user.items.gear.owned.head_mystery_201602 = true; // eslint-disable-line camelcase - expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist; - expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist; + describe('on january 15th', () => { + beforeEach(() => { + date = new Date('2024-01-15'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(01|07)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201601 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201601']).to.not.exist; + expect(items['201801']).to.exist; + expect(items['202207']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['head_mystery_201601'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201601']).to.not.exist; + expect(items['201607']).to.exist; + }); }); - it('removes unopened mystery item sets from the time travelers store', () => { - user.purchased = { - plan: { - mysteryItems: ['head_mystery_201602'], - }, - }; - expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist; - expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist; + describe('on may 1st', () => { + beforeEach(() => { + date = new Date('2024-05-01'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(05|11)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201705 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201705']).to.not.exist; + expect(items['201805']).to.exist; + expect(items['202211']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['head_mystery_201705'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201705']).to.not.exist; + expect(items['201611']).to.exist; + }); + }); + + describe('on october 21st', () => { + beforeEach(() => { + date = new Date('2024-10-21'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(10|04)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201810 = true; // eslint-disable-line camelcase + user.items.gear.owned.armor_mystery_201810 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201810']).to.not.exist; + expect(items['201910']).to.exist; + expect(items['202204']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['armor_mystery_201710'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201710']).to.not.exist; + expect(items['201604']).to.exist; + }); }); }); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 2627aed9cf..fcb0bc04a4 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -1,18 +1,26 @@ import moment from 'moment'; -function backgroundMatcher(month, oddYear) { - return function (background) { - const key = background.set.key; - const keyLength = key.length; - return parseInt(key.substring(keyLength-6, keyLength-4)) === month && parseInt(key.subtring(keyLength-2, keyLength)) % 2 === oddYear; - } +function backgroundMatcher (month1, month2, oddYear) { + return function call (key) { + if (!key.startsWith('backgrounds')) return true; + const keyLength = key.length; + const month = parseInt(key.substring(keyLength - 6, keyLength - 4), 10); + return (month === month1 || month === month2) + && parseInt(key.substring(keyLength - 2, keyLength), 10) % 2 === (oddYear ? 1 : 0); + }; } -function timeTravelersMatcher(month1, month2) { - return function (item) { - console.log(item, month1, month2) - return item; - } +function timeTravelersMatcher (month1, month2) { + return function call (item) { + const itemMonth = parseInt(item.substring(4, 6), 10); + return itemMonth === month1 || itemMonth === month2; + }; +} + +function inListMatcher (list) { + return function call (item) { + return list.indexOf(item) !== -1; + }; } export const FIRST_RELEASE_DAY = 1; @@ -21,204 +29,262 @@ export const THIRD_RELEASE_DAY = 14; export const FOURTH_RELEASE_DAY = 21; export const MONTHLY_SCHEDULE = { - 0: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(1, 7), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 1: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(2, 8), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 2: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(3, 9), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 3: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(4, 10), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 4: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(5, 11), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 5: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(6, 12), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 6: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(7, 1), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 7: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(8, 2), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 8: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(9, 3), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 9: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(10, 4), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 10: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(11, 5), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 11: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(12, 6), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, + 0: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(1, 7), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(1, 7, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'ghost_stag', + 'trex', + 'harpy', + 'sabretooth', + 'dolphin', + ]), + }, + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 1: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(2, 8), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(2, 8, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 2: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(3, 9), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(3, 9, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 3: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(4, 10), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(4, 10, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 4: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(5, 11), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(5, 11, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 5: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(6, 12), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(6, 12, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 6: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(7, 1), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(7, 1, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 7: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(8, 2), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(8, 2, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 8: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(9, 3), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(9, 3, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 9: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(10, 4), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(10, 4, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 10: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(11, 5), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(11, 5, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 11: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(12, 6), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(12, 6, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, }; export const GALA_SWITCHOVER_DAY = 21; export const GALA_SCHEDULE = { - 0: [], - 1: [], - 2: [], - 3: [], + 0: [], + 1: [], + 2: [], + 3: [], }; -export function assembleScheduledMatchers(date) { - const items = []; - const month = date instanceof moment ? date.month() : date.getMonth(); - const todayDay = date instanceof moment ? date.date() : date.getDate(); - const previousMonth = month === 0 ? 11 : month - 1; - for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { - if (day > todayDay) { - items.push(...value); - } +export function assembleScheduledMatchers (date) { + const items = []; + const month = date instanceof moment ? date.month() : date.getMonth(); + const todayDay = date instanceof moment ? date.date() : date.getDate(); + const previousMonth = month === 0 ? 11 : month - 1; + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { + if (day > todayDay) { + items.push(...value); } - for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { - if (day <= todayDay) { - items.push(...value); - } + } + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { + if (day <= todayDay) { + items.push(...value); } - let galaMonth = month; - const galaCount = Object.keys(GALA_SCHEDULE).length; - if (todayDay >= GALA_SWITCHOVER_DAY) { - galaMonth += 1; - } - items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth)]); - return items; -} \ No newline at end of file + } + let galaMonth = month; + const galaCount = Object.keys(GALA_SCHEDULE).length; + if (todayDay >= GALA_SWITCHOVER_DAY) { + galaMonth += 1; + } + items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth, 10)]); + return items; +} diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 6465252070..3dae798ddb 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -33,6 +33,8 @@ import gemsBlock from './gems'; import faq from './faq'; import timeTravelers from './time-travelers'; +import { assembleScheduledMatchers } from './constants/schedule'; + import loginIncentives from './loginIncentives'; import officialPinnedItems from './officialPinnedItems'; @@ -728,4 +730,6 @@ api.faq = faq; api.loginIncentives = loginIncentives(api); +api.assembleScheduledMatchers = assembleScheduledMatchers; + export default api; diff --git a/website/common/script/content/time-travelers.js b/website/common/script/content/time-travelers.js index 884ac1d5c1..cc717dbd46 100644 --- a/website/common/script/content/time-travelers.js +++ b/website/common/script/content/time-travelers.js @@ -7,6 +7,7 @@ import moment from 'moment'; import mysterySets from './mystery-sets'; import gear from './gear'; +import { assembleScheduledMatchers } from './constants/schedule'; const mystery = mysterySets; @@ -17,7 +18,8 @@ each(mystery, (v, k) => { if (v.items.length === 0) delete mystery[k]; }); -const timeTravelerStore = user => { +const timeTravelerStore = (user, date) => { + const availabilityMatchers = assembleScheduledMatchers(date).filter(matcher => matcher.type === 'timeTravelers').map(matcher => matcher.matcher); let ownedKeys; const { owned } = user.items.gear; const { mysteryItems } = user.purchased.plan; @@ -26,13 +28,13 @@ const timeTravelerStore = user => { ownedKeys = union(ownedKeys, unopenedGifts); return reduce(mystery, (m, v, k) => { if ( - k === 'wondercon' - || ownedKeys.indexOf(v.items[0].key) !== -1 - || (moment(k).add(1, 'months').isAfter() && moment(k).isBefore('3000-01-01')) + k !== 'wondercon' + && ownedKeys.indexOf(v.items[0].key) === -1 + && (moment(k).isAfter('3000-01-01') + || availabilityMatchers.map(matcher => matcher(k)).every(matcher => matcher === true)) ) { - return m; + m[k] = v; } - m[k] = v; return m; }, {}); }; diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 4dfc6c87b0..39dc7bd88e 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -369,7 +369,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la } } - const sets = content.timeTravelerStore(user); + const sets = content.timeTravelerStore(user, new Date()); for (const setKey of Object.keys(sets)) { const set = sets[setKey]; const category = { diff --git a/website/common/script/ops/buy/buyMysterySet.js b/website/common/script/ops/buy/buyMysterySet.js index 0602495683..dea4d07870 100644 --- a/website/common/script/ops/buy/buyMysterySet.js +++ b/website/common/script/ops/buy/buyMysterySet.js @@ -20,7 +20,7 @@ export default async function buyMysterySet (user, req = {}, analytics) { throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); } - const ref = content.timeTravelerStore(user); + const ref = content.timeTravelerStore(user, new Date()); const mysterySet = ref ? ref[key] : undefined; if (!mysterySet) {