diff --git a/package-lock.json b/package-lock.json index b82b7dfa3e..9e1c8ecb5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", + "sinon": "^15.2.0", "stripe": "^12.18.0", "superagent": "^8.1.2", "universal-analytics": "^0.5.3", @@ -94,7 +95,6 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^15.2.0", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" }, @@ -2682,7 +2682,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, "dependencies": { "type-detect": "4.0.8" } @@ -2691,7 +2690,6 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -2700,7 +2698,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, "dependencies": { "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", @@ -2711,7 +2708,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, "dependencies": { "type-detect": "4.0.8" } @@ -2719,8 +2715,7 @@ "node_modules/@sinonjs/text-encoding": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" }, "node_modules/@slack/types": { "version": "1.10.0", @@ -12806,8 +12801,7 @@ "node_modules/just-extend": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" }, "node_modules/jwa": { "version": "2.0.0", @@ -13123,8 +13117,7 @@ "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -14567,7 +14560,6 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^11.2.2", @@ -14580,7 +14572,6 @@ "version": "11.2.2", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -14588,8 +14579,7 @@ "node_modules/nise/node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "node_modules/node-abi": { "version": "2.30.1", @@ -17604,7 +17594,6 @@ "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", "deprecated": "16.1.1", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^10.3.0", @@ -17641,7 +17630,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -17650,7 +17638,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -17659,7 +17646,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -19270,7 +19256,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } diff --git a/test/common/libs/cleanupPinnedItems.test.js b/test/common/libs/cleanupPinnedItems.test.js new file mode 100644 index 0000000000..552ced19f3 --- /dev/null +++ b/test/common/libs/cleanupPinnedItems.test.js @@ -0,0 +1,123 @@ +import { + generateUser, +} from '../../helpers/common.helper'; +import cleanupPinnedItems from '../../../website/common/script/libs/cleanupPinnedItems'; + +describe.only('cleanupPinnedItems', () => { + let user; + let testPinnedItems; + let clock; + + beforeEach(() => { + user = generateUser(); + clock = sinon.useFakeTimers(new Date('2024-04-08')); + + testPinnedItems = [ + { type: 'armoire', path: 'armoire' }, + { type: 'potion', path: 'potion' }, + { type: 'background', path: 'backgrounds.backgrounds042020.heather_field' }, + { type: 'background', path: 'backgrounds.backgrounds042021.heather_field' }, + { type: 'premiumHatchingPotion', path: 'premiumHatchingPotions.Rainbow' }, + { type: 'premiumHatchingPotion', path: 'premiumHatchingPotions.StainedGlass' }, + { type: 'quests', path: 'quests.rat' }, + { type: 'quests', path: 'quests.spider' }, + { type: 'quests', path: 'quests.moon1' }, + { type: 'quests', path: 'quests.silver' }, + { type: 'marketGear', path: 'gear.flat.head_special_nye2021' }, + { type: 'gear', path: 'gear.flat.armor_special_spring2019Rogue' }, + { type: 'gear', path: 'gear.flat.armor_special_winter2021Rogue' }, + { type: 'mystery_set', path: 'mystery.201804' }, + { type: 'mystery_set', path: 'mystery.201506' }, + { type: 'bundles', path: 'bundles.farmFriends' }, + { type: 'bundles', path: 'bundles.birdBuddies' }, + { type: 'customization', path: 'skin.birdBuddies' }, + ]; + }); + + afterEach(() => { + clock.restore(); + }); + it('always keeps armoire and potion', () => { + user.pinnedItems = testPinnedItems; + + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'armoire')).to.exist; + expect(_.find(result, item => item.path === 'potion')).to.exist; + }); + + it('removes simple items that are no longer available', () => { + user.pinnedItems = testPinnedItems; + + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'backgrounds.backgrounds042021.heather_field')).to.not.exist; + expect(_.find(result, item => item.path === 'premiumHatchingPotions.Rainbow')).to.not.exist; + }); + + it('keeps simple items that are still available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'backgrounds.backgrounds042020.heather_field')).to.exist; + expect(_.find(result, item => item.path === 'premiumHatchingPotions.StainedGlass')).to.exist; + }); + + it('removes gear that is no longer available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'gear.flat.armor_special_winter2021Rogue')).to.not.exist; + }); + + it('keeps gear that is still available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'gear.flat.armor_special_spring2019Rogue')).to.exist; + }); + + it('keeps gear that is not seasonal', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'gear.flat.head_special_nye2021')).to.exist; + }); + + it('removes time traveler gear that is no longer available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'mystery.201506')).to.not.exist; + }); + + it('keeps time traveler gear that is still available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'mystery.201804')).to.exist; + }); + + it('removes quests that are no longer available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'quests.rat')).to.not.exist; + expect(_.find(result, item => item.path === 'quests.silver')).to.not.exist; + }); + + it('keeps quests that are still available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'quests.spider')).to.exist; + }); + + it('keeps quests that are not seasonal', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'quests.moon1')).to.exist; + }); + + it('removes bundles that are no longer available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'bundles.farmFriends')).to.not.exist; + }); + + it('keeps bundles that are still available', () => { + user.pinnedItems = testPinnedItems; + const result = cleanupPinnedItems(user); + expect(_.find(result, item => item.path === 'bundles.birdBuddies')).to.exist; + }); +}); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 193f2e3791..03ebc65d13 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -835,7 +835,7 @@ function makeMatcherClass () { }; } -export function getScheduleMatchingGroup (type, date) { +export function getAllScheduleMatchingGroups (date) { const checkedDate = date || new Date(); if (cacheDate !== null && (getDay(checkedDate) !== getDay(cacheDate) || getMonth(checkedDate) !== getMonth(cacheDate))) { @@ -869,7 +869,7 @@ export function getScheduleMatchingGroup (type, date) { }, }; } - return cachedScheduleMatchers[type]; + return matchingGroups[type]; } export function getCurrentGalaKey (date) { diff --git a/website/common/script/libs/cleanupPinnedItems.js b/website/common/script/libs/cleanupPinnedItems.js new file mode 100644 index 0000000000..8acd8fe8ae --- /dev/null +++ b/website/common/script/libs/cleanupPinnedItems.js @@ -0,0 +1,50 @@ +import getItemByPathAndType from './getItemByPathAndType'; +import { getAllScheduleMatchingGroups } from '../content/constants/schedule'; + +const simpleSeasonalPins = [ + 'background', + 'premiumHatchingPotion', + 'mystery_set', + 'bundles', + 'seasonalQuest', +]; + +const detailSeasonalPins = [ + 'quests', + 'gear', +]; + +export default function cleanupPinnedItems (user) { + const matchers = getAllScheduleMatchingGroups(); + + const items = user.pinnedItems + .filter(pinnedItem => { + const { type } = pinnedItem; + const key = pinnedItem.path.split('.').slice(-1)[0]; + if (simpleSeasonalPins.indexOf(type) != -1) { + if (type === 'background') { + return matchers.backgrounds.match(pinnedItem.path.split('.')[1]); + } if (type === 'premiumHatchingPotion') { + return matchers.premiumHatchingPotions.match(key); + } if (type === 'mystery_set') { + return matchers.timeTravelers.match(key); + } if (type === 'seasonalQuest') { + return matchers.seasonalQuests.match(key); + } + return matchers[type].match(key); + } if (detailSeasonalPins.indexOf(type) != -1) { + const item = getItemByPathAndType(type, pinnedItem.path); + if (type === 'gear' && item.klass === 'special') { + return matchers.seasonalGear.match(item.set); + } if (type === 'quests' && item.category === 'pet') { + return matchers.petQuests.match(item.key); + } if (type === 'quests' && item.category === 'hatchingPotion') { + return matchers.hatchingPotionQuests.match(item.key); + } + return true; + } + return true; + }); + + return items; +} diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js index 6ee8930b3f..74587dfd95 100644 --- a/website/server/libs/cron.js +++ b/website/server/libs/cron.js @@ -7,6 +7,7 @@ import common from '../../common'; import { preenUserHistory } from './preening'; import { sleep } from './sleep'; import { revealMysteryItems } from './payments/subscriptions'; +import cleanupPinnedItems from '../../common/script/libs/cleanupPinnedItems'; const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true'; @@ -499,6 +500,10 @@ export async function cron (options = {}) { _.merge(progress, { down: 0, up: 0, collectedItems: 0 }); } + if (user.pinnedItems && user.pinnedItems.length > 0) { + user.pinnedItems = cleanupPinnedItems(user); + } + // Send notification for changes in HP and MP. // First remove a possible previous cron notification because // we don't want to flood the users with many cron notifications at once.