diff --git a/.gitignore b/.gitignore
index 85493ee023..f80fc6ce3d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@ i18n_cache
apidoc/html
*.swp
.idea*
-config.json
+config*.json
npm-debug.log*
lib
newrelic_agent.log
@@ -48,3 +48,4 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data
+/.nyc_output
diff --git a/config.json.example b/config.json.example
index ebca73675c..520c122003 100644
--- a/config.json.example
+++ b/config.json.example
@@ -89,5 +89,8 @@
"REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PORT": "1234",
"REDIS_PASSWORD": "12345678",
- "TRUSTED_DOMAINS": "localhost,https://habitica.com"
+ "TRUSTED_DOMAINS": "localhost,https://habitica.com",
+ "TIME_TRAVEL_ENABLED": "false",
+ "DEBUG_ENABLED": "false",
+ "CONTENT_SWITCHOVER_TIME_OFFSET": 8
}
diff --git a/gulp/gulp-tests.js b/gulp/gulp-tests.js
index f46fd53fa7..64ac63902f 100644
--- a/gulp/gulp-tests.js
+++ b/gulp/gulp-tests.js
@@ -44,8 +44,8 @@ function runInChildProcess (command, options = {}, envVariables = '') {
return done => pipe(exec(testBin(command, envVariables), options, done));
}
-function integrationTestCommand (testDir, coverageDir) {
- return `istanbul cover --dir coverage/${coverageDir} --report lcovonly node_modules/mocha/bin/_mocha -- ${testDir} --recursive --require ./test/helpers/start-server`;
+function integrationTestCommand (testDir) {
+ return `nyc --silent --no-clean mocha ${testDir} --recursive --require ./test/helpers/start-server`;
}
/* Test task definitions */
@@ -148,7 +148,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
gulp.task(
'test:api:unit:run',
- runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')),
+ runInChildProcess(integrationTestCommand('test/api/unit')),
);
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
@@ -156,7 +156,7 @@ gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'tes
gulp.task('test:api-v3:integration', gulp.series(
'test:prepare:mongo',
runInChildProcess(
- integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
+ integrationTestCommand('test/api/v3/integration'),
LIMIT_MAX_BUFFER_OPTIONS,
),
));
@@ -175,7 +175,7 @@ gulp.task('test:api-v3:integration:separate-server', runInChildProcess(
gulp.task('test:api-v4:integration', gulp.series(
'test:prepare:mongo',
runInChildProcess(
- integrationTestCommand('test/api/v4', 'api-v4-integration'),
+ integrationTestCommand('test/api/v4'),
LIMIT_MAX_BUFFER_OPTIONS,
),
));
diff --git a/habitica-images b/habitica-images
index aa72332019..617a3d6e6c 160000
--- a/habitica-images
+++ b/habitica-images
@@ -1 +1 @@
-Subproject commit aa723320199d7f03ce749d431b46e8d7f95cc8de
+Subproject commit 617a3d6e6cd870172690a22b4dd16327f5e9d997
diff --git a/migrations/archive/2024/20240621_veteran_pets.js b/migrations/archive/2024/20240621_veteran_pets.js
new file mode 100644
index 0000000000..bc4617084a
--- /dev/null
+++ b/migrations/archive/2024/20240621_veteran_pets.js
@@ -0,0 +1,149 @@
+/* eslint-disable no-console */
+const MIGRATION_NAME = '20240621_veteran_pet_ladder';
+import { model as User } from '../../../website/server/models/user';
+
+const progressCount = 1000;
+let count = 0;
+
+async function updateUser (user) {
+ count++;
+
+ const set = {};
+ let push = { notifications: { $each: [] }};
+
+ set.migration = MIGRATION_NAME;
+ if (user.items.pets['Dragon-Veteran']) {
+ set['items.pets.Cactus-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_cactus',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Cactus and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else if (user.items.pets['Fox-Veteran']) {
+ set['items.pets.Dragon-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_dragon',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Dragon and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else if (user.items.pets['Bear-Veteran']) {
+ set['items.pets.Fox-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_fox',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Fox and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else if (user.items.pets['Lion-Veteran']) {
+ set['items.pets.Bear-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_bear',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Bear and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else if (user.items.pets['Tiger-Veteran']) {
+ set['items.pets.Lion-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_lion',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Lion and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else if (user.items.pets['Wolf-Veteran']) {
+ set['items.pets.Tiger-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_tiger',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Tiger and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ } else {
+ set['items.pets.Wolf-Veteran'] = 5;
+ push.notifications.$each.push({
+ type: 'ITEM_RECEIVED',
+ data: {
+ icon: 'icon_pet_veteran_wolf',
+ title: 'You’ve received a Veteran Pet!',
+ text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Wolf and 24 Gems!',
+ destination: '/inventory/stable',
+ },
+ seen: false,
+ });
+ }
+
+ if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
+
+ await user.updateBalance(
+ 6,
+ 'admin_update_balance',
+ '',
+ 'Veteran Ladder award',
+ );
+
+ return await User.updateOne(
+ { _id: user._id },
+ { $set: set, $push: push, $inc: { balance: 6 } },
+ ).exec();
+}
+
+export default async function processUsers () {
+ let query = {
+ migration: {$ne: MIGRATION_NAME},
+ 'auth.timestamps.loggedin': { $gt: new Date('2024-05-21') },
+ };
+
+ const fields = {
+ _id: 1,
+ items: 1,
+ migration: 1,
+ contributor: 1,
+ };
+
+ while (true) { // eslint-disable-line no-constant-condition
+ const users = await User // eslint-disable-line no-await-in-loop
+ .find(query)
+ .limit(250)
+ .sort({_id: 1})
+ .select(fields)
+ .exec();
+
+ if (users.length === 0) {
+ console.warn('All appropriate users found and modified.');
+ console.warn(`\n${count} users processed\n`);
+ break;
+ } else {
+ query._id = {
+ $gt: users[users.length - 1],
+ };
+ }
+
+ await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
+ }
+};
diff --git a/package-lock.json b/package-lock.json
index 52ddb3a89a..f7d36bd71d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "habitica",
- "version": "5.25.2",
+ "version": "5.25.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
- "version": "5.25.2",
+ "version": "5.25.8",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -72,6 +72,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",
@@ -89,13 +90,11 @@
"chai-moment": "^0.1.0",
"chalk": "^5.3.0",
"cross-spawn": "^7.0.3",
- "expect.js": "^0.3.1",
- "istanbul": "^1.1.0-alpha.1",
"mocha": "^5.1.1",
"monk": "^7.3.4",
+ "nyc": "^15.1.0",
"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"
},
@@ -2814,6 +2813,86 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw=="
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -3254,7 +3333,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"
}
@@ -3263,7 +3341,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"
}
@@ -3272,7 +3349,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",
@@ -3283,7 +3359,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"
}
@@ -3291,8 +3366,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",
@@ -3852,6 +3926,28 @@
"node": ">= 6.0.0"
}
},
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aggregate-error/node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -4114,18 +4210,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/append-transform": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz",
- "integrity": "sha512-Yisb7ew0ZEyDtRYQ+b+26o9KbiYPFxwcsxKzbssigzRRMJ9LpExPVUg6Fos7eP7yP3q7///tzze4nm4lTptPBw==",
- "dev": true,
- "dependencies": {
- "default-require-extensions": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/apple-auth": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.9.tgz",
@@ -4684,112 +4768,6 @@
"proxy-from-env": "^1.1.0"
}
},
- "node_modules/babel-code-frame": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
- "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
- "dev": true,
- "dependencies": {
- "chalk": "^1.1.3",
- "esutils": "^2.0.2",
- "js-tokens": "^3.0.2"
- }
- },
- "node_modules/babel-code-frame/node_modules/ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-code-frame/node_modules/ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-code-frame/node_modules/chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-code-frame/node_modules/js-tokens": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
- "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==",
- "dev": true
- },
- "node_modules/babel-code-frame/node_modules/strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-generator": {
- "version": "6.26.1",
- "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
- "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
- "dev": true,
- "dependencies": {
- "babel-messages": "^6.23.0",
- "babel-runtime": "^6.26.0",
- "babel-types": "^6.26.0",
- "detect-indent": "^4.0.0",
- "jsesc": "^1.3.0",
- "lodash": "^4.17.4",
- "source-map": "^0.5.7",
- "trim-right": "^1.0.1"
- }
- },
- "node_modules/babel-generator/node_modules/jsesc": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
- "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==",
- "dev": true,
- "bin": {
- "jsesc": "bin/jsesc"
- }
- },
- "node_modules/babel-generator/node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-messages": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
- "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==",
- "dev": true,
- "dependencies": {
- "babel-runtime": "^6.22.0"
- }
- },
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz",
@@ -4841,106 +4819,6 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
- "node_modules/babel-runtime": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
- "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
- "dev": true,
- "dependencies": {
- "core-js": "^2.4.0",
- "regenerator-runtime": "^0.11.0"
- }
- },
- "node_modules/babel-runtime/node_modules/regenerator-runtime": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
- "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
- "dev": true
- },
- "node_modules/babel-template": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
- "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==",
- "dev": true,
- "dependencies": {
- "babel-runtime": "^6.26.0",
- "babel-traverse": "^6.26.0",
- "babel-types": "^6.26.0",
- "babylon": "^6.18.0",
- "lodash": "^4.17.4"
- }
- },
- "node_modules/babel-traverse": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
- "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==",
- "dev": true,
- "dependencies": {
- "babel-code-frame": "^6.26.0",
- "babel-messages": "^6.23.0",
- "babel-runtime": "^6.26.0",
- "babel-types": "^6.26.0",
- "babylon": "^6.18.0",
- "debug": "^2.6.8",
- "globals": "^9.18.0",
- "invariant": "^2.2.2",
- "lodash": "^4.17.4"
- }
- },
- "node_modules/babel-traverse/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/babel-traverse/node_modules/globals": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
- "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babel-traverse/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
- "node_modules/babel-types": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
- "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==",
- "dev": true,
- "dependencies": {
- "babel-runtime": "^6.26.0",
- "esutils": "^2.0.2",
- "lodash": "^4.17.4",
- "to-fast-properties": "^1.0.3"
- }
- },
- "node_modules/babel-types/node_modules/to-fast-properties": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
- "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/babylon": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
- "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
- "dev": true,
- "bin": {
- "babylon": "bin/babylon.js"
- }
- },
"node_modules/bach": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz",
@@ -6006,6 +5884,36 @@
"node": ">=8"
}
},
+ "node_modules/caching-transform": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
+ "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==",
+ "dev": true,
+ "dependencies": {
+ "hasha": "^5.0.0",
+ "make-dir": "^3.0.0",
+ "package-hash": "^4.0.0",
+ "write-file-atomic": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/caching-transform/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
@@ -6240,6 +6148,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -6885,14 +6802,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/core-js": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
- "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
- "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
- "dev": true,
- "hasInstallScript": true
- },
"node_modules/core-js-compat": {
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
@@ -7360,18 +7269,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/default-require-extensions": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
- "integrity": "sha512-Dn2eAftOqXhNXs5f/Xjn7QTZ6kDYkx7u0EXQInN1oyYwsZysu11q7oTtaKcbzLxZRJiDHa8VmwpWmb4lY5FqgA==",
- "dev": true,
- "dependencies": {
- "strip-bom": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/default-resolution": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz",
@@ -7488,18 +7385,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/detect-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
- "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==",
- "dev": true,
- "dependencies": {
- "repeating": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
@@ -8002,6 +7887,12 @@
"node": ">=0.10"
}
},
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true
+ },
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
@@ -9399,12 +9290,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/expect.js": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz",
- "integrity": "sha512-okDF/FAPEul1ZFLae4hrgpIqAeapoo5TRdcg/lD0iN9S3GWrBFIJwNezGH1DMtIz+RxU4RrFmMq7WUUvDg3J6A==",
- "dev": true
- },
"node_modules/expose-loader": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz",
@@ -9960,36 +9845,6 @@
"node": ">=4"
}
},
- "node_modules/fileset": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
- "integrity": "sha512-UxowFKnAFIwtmSxgKjWAVgjE3Fk7MQJT0ZIyl0NwIFZTrx4913rLaonGJ84V+x/2+w/pe4ULHRns+GZPs1TVuw==",
- "dev": true,
- "dependencies": {
- "glob": "^7.0.3",
- "minimatch": "^3.0.3"
- }
- },
- "node_modules/fileset/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -10281,6 +10136,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/foreground-child": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
+ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -10402,6 +10270,26 @@
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/fromentries": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
+ "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -10464,19 +10352,6 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -10600,6 +10475,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-pixels": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/get-pixels/-/get-pixels-3.3.3.tgz",
@@ -11909,7 +11793,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
- "devOptional": true,
+ "optional": true,
"dependencies": {
"ansi-regex": "^2.0.0"
},
@@ -11921,7 +11805,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
- "devOptional": true,
+ "optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -11934,15 +11818,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
- "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/has-property-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
@@ -12074,6 +11949,31 @@
"node": ">=0.10.0"
}
},
+ "node_modules/hasha": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
+ "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
+ "dev": true,
+ "dependencies": {
+ "is-stream": "^2.0.0",
+ "type-fest": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/hasha/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -12147,6 +12047,12 @@
],
"optional": true
},
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@@ -12711,15 +12617,6 @@
"node": ">=4"
}
},
- "node_modules/invariant": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
- "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
- "dev": true,
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
"node_modules/invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
@@ -12926,7 +12823,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
"integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
- "devOptional": true,
+ "optional": true,
"engines": {
"node": ">=0.10.0"
},
@@ -13261,216 +13158,39 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
},
- "node_modules/istanbul": {
- "version": "1.1.0-alpha.1",
- "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.1.0-alpha.1.tgz",
- "integrity": "sha512-acLlAtOI1itxA7agb2+jKoRFjQa9vuV2G48jUyerkrPXaSBh0q4hMre7qYhGoskk8R5XFbt6v5mOITAlf5LFgQ==",
+ "node_modules/istanbul-lib-processinfo": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz",
+ "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==",
"dev": true,
"dependencies": {
- "abbrev": "1.0.x",
- "async": "1.x",
- "istanbul-api": "^1.1.0-alpha",
- "js-yaml": "3.x",
- "mkdirp": "0.5.x",
- "nopt": "3.x",
- "which": "^1.1.1",
- "wordwrap": "^1.0.0"
- },
- "bin": {
- "istanbul": "lib/cli.js"
- }
- },
- "node_modules/istanbul-api": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz",
- "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==",
- "dev": true,
- "dependencies": {
- "async": "^2.1.4",
- "fileset": "^2.0.2",
- "istanbul-lib-coverage": "^1.2.1",
- "istanbul-lib-hook": "^1.2.2",
- "istanbul-lib-instrument": "^1.10.2",
- "istanbul-lib-report": "^1.1.5",
- "istanbul-lib-source-maps": "^1.2.6",
- "istanbul-reports": "^1.5.1",
- "js-yaml": "^3.7.0",
- "mkdirp": "^0.5.1",
- "once": "^1.4.0"
- }
- },
- "node_modules/istanbul-api/node_modules/async": {
- "version": "2.6.4",
- "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
- "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
- "dev": true,
- "dependencies": {
- "lodash": "^4.17.14"
- }
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz",
- "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==",
- "dev": true
- },
- "node_modules/istanbul-lib-hook": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz",
- "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==",
- "dev": true,
- "dependencies": {
- "append-transform": "^0.4.0"
- }
- },
- "node_modules/istanbul-lib-instrument": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz",
- "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==",
- "dev": true,
- "dependencies": {
- "babel-generator": "^6.18.0",
- "babel-template": "^6.16.0",
- "babel-traverse": "^6.18.0",
- "babel-types": "^6.18.0",
- "babylon": "^6.18.0",
- "istanbul-lib-coverage": "^1.2.1",
- "semver": "^5.3.0"
- }
- },
- "node_modules/istanbul-lib-instrument/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true,
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz",
- "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==",
- "dev": true,
- "dependencies": {
- "istanbul-lib-coverage": "^1.2.1",
- "mkdirp": "^0.5.1",
- "path-parse": "^1.0.5",
- "supports-color": "^3.1.2"
- }
- },
- "node_modules/istanbul-lib-report/node_modules/supports-color": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
- "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==",
- "dev": true,
- "dependencies": {
- "has-flag": "^1.0.0"
+ "archy": "^1.0.0",
+ "cross-spawn": "^7.0.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "p-map": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "uuid": "^8.3.2"
},
"engines": {
- "node": ">=0.8.0"
+ "node": ">=8"
}
},
- "node_modules/istanbul-lib-source-maps": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz",
- "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==",
- "dev": true,
- "dependencies": {
- "debug": "^3.1.0",
- "istanbul-lib-coverage": "^1.2.1",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.6.1",
- "source-map": "^0.5.3"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "dev": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "node_modules/istanbul-lib-processinfo/node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/istanbul-reports": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz",
- "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==",
+ "node_modules/istanbul-lib-processinfo/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
- "dependencies": {
- "handlebars": "^4.0.3"
- }
- },
- "node_modules/istanbul/node_modules/async": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
- "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==",
- "dev": true
- },
- "node_modules/istanbul/node_modules/nopt": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
- "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==",
- "dev": true,
- "dependencies": {
- "abbrev": "1"
- },
"bin": {
- "nopt": "bin/nopt.js"
- }
- },
- "node_modules/istanbul/node_modules/which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
- "dev": true,
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "which": "bin/which"
+ "uuid": "dist/bin/uuid"
}
},
"node_modules/isurl": {
@@ -13783,8 +13503,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",
@@ -14103,11 +13822,16 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
+ "node_modules/lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
+ "dev": true
+ },
"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",
@@ -14213,18 +13937,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
"node_modules/loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
@@ -15553,7 +15265,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",
@@ -15566,7 +15277,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"
}
@@ -15574,8 +15284,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",
@@ -15687,6 +15396,18 @@
"node": "*"
}
},
+ "node_modules/node-preload": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
+ "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==",
+ "dev": true,
+ "dependencies": {
+ "process-on-spawn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -15894,6 +15615,453 @@
"node": ">=0.10.0"
}
},
+ "node_modules/nyc": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz",
+ "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "caching-transform": "^4.0.0",
+ "convert-source-map": "^1.7.0",
+ "decamelize": "^1.2.0",
+ "find-cache-dir": "^3.2.0",
+ "find-up": "^4.1.0",
+ "foreground-child": "^2.0.0",
+ "get-package-type": "^0.1.0",
+ "glob": "^7.1.6",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-hook": "^3.0.0",
+ "istanbul-lib-instrument": "^4.0.0",
+ "istanbul-lib-processinfo": "^2.0.2",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.0.2",
+ "make-dir": "^3.0.0",
+ "node-preload": "^0.2.1",
+ "p-map": "^3.0.0",
+ "process-on-spawn": "^1.0.0",
+ "resolve-from": "^5.0.0",
+ "rimraf": "^3.0.0",
+ "signal-exit": "^3.0.2",
+ "spawn-wrap": "^2.0.0",
+ "test-exclude": "^6.0.0",
+ "yargs": "^15.0.2"
+ },
+ "bin": {
+ "nyc": "bin/nyc.js"
+ },
+ "engines": {
+ "node": ">=8.9"
+ }
+ },
+ "node_modules/nyc/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/nyc/node_modules/append-transform": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
+ "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
+ "dev": true,
+ "dependencies": {
+ "default-require-extensions": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/nyc/node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/nyc/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/nyc/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/nyc/node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
+ },
+ "node_modules/nyc/node_modules/default-require-extensions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
+ "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==",
+ "dev": true,
+ "dependencies": {
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nyc/node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
+ "node_modules/nyc/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/nyc/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/nyc/node_modules/has-flag": {
+ "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"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-hook": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz",
+ "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==",
+ "dev": true,
+ "dependencies": {
+ "append-transform": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-instrument": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz",
+ "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.7.5",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.0.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-report/node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-report/node_modules/semver": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nyc/node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nyc/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "node_modules/nyc/node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/supports-color": {
+ "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"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "dev": true
+ },
+ "node_modules/nyc/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "node_modules/nyc/node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nyc/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/oauth": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz",
@@ -16365,6 +16533,18 @@
"node": ">=6"
}
},
+ "node_modules/p-map": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
+ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
+ "dev": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/p-map-series": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz",
@@ -16417,6 +16597,21 @@
"node": ">=6"
}
},
+ "node_modules/package-hash": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
+ "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.15",
+ "hasha": "^5.0.0",
+ "lodash.flattendeep": "^4.4.0",
+ "release-zalgo": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -17110,6 +17305,18 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
+ "node_modules/process-on-spawn": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz",
+ "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==",
+ "dev": true,
+ "dependencies": {
+ "fromentries": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -17623,6 +17830,18 @@
"jsesc": "bin/jsesc"
}
},
+ "node_modules/release-zalgo": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
+ "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==",
+ "dev": true,
+ "dependencies": {
+ "es6-error": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/remove-bom-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz",
@@ -17678,7 +17897,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
"integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==",
- "devOptional": true,
+ "optional": true,
"dependencies": {
"is-finite": "^1.0.0"
},
@@ -18616,7 +18835,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",
@@ -18653,7 +18871,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"
}
@@ -18662,7 +18879,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"
}
@@ -18671,7 +18887,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"
},
@@ -18931,6 +19146,38 @@
"memory-pager": "^1.0.2"
}
},
+ "node_modules/spawn-wrap": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
+ "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^2.0.0",
+ "is-windows": "^1.0.2",
+ "make-dir": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "signal-exit": "^3.0.2",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/spawn-wrap/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@@ -19542,7 +19789,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
- "devOptional": true,
+ "optional": true,
"engines": {
"node": ">=0.8.0"
}
@@ -19936,6 +20183,40 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
@@ -20173,15 +20454,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/trim-right": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
- "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
@@ -20269,7 +20541,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"
}
@@ -20363,6 +20634,15 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
+ "node_modules/typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dev": true,
+ "dependencies": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@@ -21510,6 +21790,18 @@
"node": ">=4"
}
},
+ "node_modules/write-file-atomic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "is-typedarray": "^1.0.0",
+ "signal-exit": "^3.0.2",
+ "typedarray-to-buffer": "^3.1.5"
+ }
+ },
"node_modules/xml-crypto": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-0.10.1.tgz",
diff --git a/package.json b/package.json
index ea5bbc0c52..f166b266f1 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
- "version": "5.25.2",
+ "version": "5.25.8",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -67,6 +67,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",
@@ -93,11 +94,11 @@
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
"test:api-v4:integration": "gulp test:api-v4:integration",
"test:api-v4:integration:separate-server": "NODE_ENV=test gulp test:api-v4:integration:separate-server",
- "test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
- "test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
- "test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
+ "test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
+ "test:common": "nyc --silent --no-clean mocha test/common --recursive",
+ "test:content": "nyc --silent --no-clean mocha test/content --recursive",
"test:nodemon": "gulp test:nodemon",
- "coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
+ "coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
"sprites": "gulp sprites:compile",
"client:dev": "cd website/client && npm run serve",
"client:build": "cd website/client && npm run build",
@@ -115,13 +116,11 @@
"chai-moment": "^0.1.0",
"chalk": "^5.3.0",
"cross-spawn": "^7.0.3",
- "expect.js": "^0.3.1",
- "istanbul": "^1.1.0-alpha.1",
"mocha": "^5.1.1",
"monk": "^7.3.4",
+ "nyc": "^15.1.0",
"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"
}
diff --git a/test/api/unit/libs/content.test.js b/test/api/unit/libs/content.test.js
index 368d8fe9fd..53c85e4205 100644
--- a/test/api/unit/libs/content.test.js
+++ b/test/api/unit/libs/content.test.js
@@ -1,5 +1,9 @@
+import fs from 'fs';
import * as contentLib from '../../../../website/server/libs/content';
import content from '../../../../website/common/script/content';
+import {
+ generateRes,
+} from '../../../helpers/api-unit.helper';
describe('contentLib', () => {
describe('CONTENT_CACHE_PATH', () => {
@@ -13,5 +17,90 @@ describe('contentLib', () => {
contentLib.getLocalizedContentResponse();
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
});
+
+ it('removes keys from the content data', () => {
+ const response = contentLib.localizeContentData(content, 'en', { backgroundsFlat: true, dropHatchingPotions: true });
+ expect(response.backgroundsFlat).to.not.exist;
+ expect(response.backgrounds).to.exist;
+ expect(response.dropHatchingPotions).to.not.exist;
+ expect(response.hatchingPotions).to.exist;
+ });
+
+ it('removes nested keys from the content data', () => {
+ const response = contentLib.localizeContentData(content, 'en', { gear: { tree: true } });
+ expect(response.gear.tree).to.not.exist;
+ expect(response.gear.flat).to.exist;
+ });
+ });
+
+ it('generates a hash for a filter', () => {
+ const hash = contentLib.hashForFilter('backgroundsFlat,gear.flat');
+ expect(hash).to.equal('-1791877526');
+ });
+
+ it('serves content', () => {
+ const resSpy = generateRes();
+ contentLib.serveContent(resSpy, 'en', '', false);
+ expect(resSpy.send).to.have.been.calledOnce;
+ });
+
+ it('serves filtered content', () => {
+ const resSpy = generateRes();
+ contentLib.serveContent(resSpy, 'en', 'backgroundsFlat,gear.flat', false);
+ expect(resSpy.send).to.have.been.calledOnce;
+ });
+
+ describe('caches content', async () => {
+ let resSpy;
+ beforeEach(() => {
+ resSpy = generateRes();
+ if (fs.existsSync(contentLib.CONTENT_CACHE_PATH)) {
+ fs.rmdirSync(contentLib.CONTENT_CACHE_PATH, { recursive: true });
+ }
+ fs.mkdirSync(contentLib.CONTENT_CACHE_PATH);
+ });
+
+ it('does not cache requests in development mode', async () => {
+ contentLib.serveContent(resSpy, 'en', '', false);
+ expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false;
+ });
+
+ it('caches unfiltered requests', async () => {
+ expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false;
+ contentLib.serveContent(resSpy, 'en', '', true);
+ expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.true;
+ });
+
+ it('serves cached requests', async () => {
+ fs.writeFileSync(
+ `${contentLib.CONTENT_CACHE_PATH}en.json`,
+ '{"success": true, "data": {"all": {}}}',
+ 'utf8',
+ );
+ contentLib.serveContent(resSpy, 'en', '', true);
+ expect(resSpy.sendFile).to.have.been.calledOnce;
+ expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en.json`);
+ });
+
+ it('caches filtered requests', async () => {
+ const filter = 'backgroundsFlat,gear.flat';
+ const hash = contentLib.hashForFilter(filter);
+ expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.false;
+ contentLib.serveContent(resSpy, 'en', filter, true);
+ expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.true;
+ });
+
+ it('serves filtered cached requests', async () => {
+ const filter = 'backgroundsFlat,gear.flat';
+ const hash = contentLib.hashForFilter(filter);
+ fs.writeFileSync(
+ `${contentLib.CONTENT_CACHE_PATH}en${hash}.json`,
+ '{"success": true, "data": {}}',
+ 'utf8',
+ );
+ contentLib.serveContent(resSpy, 'en', filter, true);
+ expect(resSpy.sendFile).to.have.been.calledOnce;
+ expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`);
+ });
});
});
diff --git a/test/api/unit/libs/items/utils.test.js b/test/api/unit/libs/items/utils.test.js
index d1bf1f126e..9ff116ef95 100644
--- a/test/api/unit/libs/items/utils.test.js
+++ b/test/api/unit/libs/items/utils.test.js
@@ -117,7 +117,7 @@ describe('Items Utils', () => {
it('converts values for owned gear to true/false', () => {
expect(castItemVal('items.gear.owned.shield_warrior_0', 'true')).to.equal(true);
expect(castItemVal('items.gear.owned.invalid', 'false')).to.equal(false);
- expect(castItemVal('items.gear.owned.invalid', 'null')).to.equal(false);
+ expect(castItemVal('items.gear.owned.invalid', 'null')).to.equal(undefined);
expect(castItemVal('items.gear.owned.invalid', 'truthy')).to.equal(true);
expect(castItemVal('items.gear.owned.invalid', 0)).to.equal(false);
});
diff --git a/test/api/unit/middlewares/ensureDevelopmentMode.js b/test/api/unit/middlewares/ensureDevelopmentMode.js
new file mode 100644
index 0000000000..da5d2cffc0
--- /dev/null
+++ b/test/api/unit/middlewares/ensureDevelopmentMode.js
@@ -0,0 +1,51 @@
+/* eslint-disable global-require */
+import nconf from 'nconf';
+import {
+ generateRes,
+ generateReq,
+ generateNext,
+} from '../../../helpers/api-unit.helper';
+import ensureDevelopmentMode from '../../../../website/server/middlewares/ensureDevelopmentMode';
+import { NotFound } from '../../../../website/server/libs/errors';
+
+describe('developmentMode middleware', () => {
+ let res; let req; let next;
+ let nconfStub;
+
+ beforeEach(() => {
+ res = generateRes();
+ req = generateReq();
+ next = generateNext();
+ nconfStub = sandbox.stub(nconf, 'get');
+ });
+
+ it('returns not found when on production URL', () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
+
+ ensureDevelopmentMode(req, res, next);
+
+ const calledWith = next.getCall(0).args;
+ expect(calledWith[0] instanceof NotFound).to.equal(true);
+ });
+
+ it('returns not found when intentionally disabled', () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
+ nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
+
+ ensureDevelopmentMode(req, res, next);
+
+ const calledWith = next.getCall(0).args;
+ expect(calledWith[0] instanceof NotFound).to.equal(true);
+ });
+
+ it('passes when enabled and on non-production URL', () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
+
+ ensureDevelopmentMode(req, res, next);
+
+ expect(next).to.be.calledOnce;
+ expect(next.args[0]).to.be.empty;
+ });
+});
diff --git a/test/api/unit/middlewares/ensureDevelpmentMode.js b/test/api/unit/middlewares/ensureDevelpmentMode.js
deleted file mode 100644
index 39bbb2fe6f..0000000000
--- a/test/api/unit/middlewares/ensureDevelpmentMode.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* eslint-disable global-require */
-import nconf from 'nconf';
-import {
- generateRes,
- generateReq,
- generateNext,
-} from '../../../helpers/api-unit.helper';
-import ensureDevelpmentMode from '../../../../website/server/middlewares/ensureDevelpmentMode';
-import { NotFound } from '../../../../website/server/libs/errors';
-
-describe('developmentMode middleware', () => {
- let res; let req; let
- next;
-
- beforeEach(() => {
- res = generateRes();
- req = generateReq();
- next = generateNext();
- });
-
- it('returns not found when in production mode', () => {
- sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
-
- ensureDevelpmentMode(req, res, next);
-
- const calledWith = next.getCall(0).args;
- expect(calledWith[0] instanceof NotFound).to.equal(true);
- });
-
- it('passes when not in production', () => {
- sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
-
- ensureDevelpmentMode(req, res, next);
-
- expect(next).to.be.calledOnce;
- expect(next.args[0]).to.be.empty;
- });
-});
diff --git a/test/api/unit/middlewares/ensureTimeTravelMode.js b/test/api/unit/middlewares/ensureTimeTravelMode.js
new file mode 100644
index 0000000000..6be7d5f4eb
--- /dev/null
+++ b/test/api/unit/middlewares/ensureTimeTravelMode.js
@@ -0,0 +1,51 @@
+/* eslint-disable global-require */
+import nconf from 'nconf';
+import {
+ generateRes,
+ generateReq,
+ generateNext,
+} from '../../../helpers/api-unit.helper';
+import { NotFound } from '../../../../website/server/libs/errors';
+import ensureTimeTravelMode from '../../../../website/server/middlewares/ensureTimeTravelMode';
+
+describe('timetravelMode middleware', () => {
+ let res; let req; let next;
+ let nconfStub;
+
+ beforeEach(() => {
+ res = generateRes();
+ req = generateReq();
+ next = generateNext();
+ nconfStub = sandbox.stub(nconf, 'get');
+ });
+
+ it('returns not found when using production URL', () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
+ nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
+
+ ensureTimeTravelMode(req, res, next);
+
+ const calledWith = next.getCall(0).args;
+ expect(calledWith[0] instanceof NotFound).to.equal(true);
+ });
+
+ it('returns not found when not in time travel mode', () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
+ nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
+
+ ensureTimeTravelMode(req, res, next);
+
+ const calledWith = next.getCall(0).args;
+ expect(calledWith[0] instanceof NotFound).to.equal(true);
+ });
+
+ it('passes when in time travel mode', () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
+
+ ensureTimeTravelMode(req, res, next);
+
+ expect(next).to.be.calledOnce;
+ expect(next.args[0]).to.be.empty;
+ });
+});
diff --git a/test/api/v3/integration/content/GET-content.test.js b/test/api/v3/integration/content/GET-content.test.js
index a241058be5..0dbd2779e4 100644
--- a/test/api/v3/integration/content/GET-content.test.js
+++ b/test/api/v3/integration/content/GET-content.test.js
@@ -22,4 +22,38 @@ describe('GET /content', () => {
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText'));
});
+
+ it('does not filter content for regular requests', async () => {
+ const res = await requester().get('/content');
+ expect(res).to.have.nested.property('backgrounds.backgrounds062014');
+ expect(res).to.have.nested.property('gear.tree');
+ });
+
+ it('filters content automatically for iOS requests', async () => {
+ const res = await requester(null, { 'x-client': 'habitica-ios' }).get('/content');
+ expect(res).to.have.nested.property('appearances.background.beach');
+ expect(res).to.not.have.nested.property('backgrounds.backgrounds062014');
+ expect(res).to.not.have.property('backgroundsFlat');
+ expect(res).to.not.have.nested.property('gear.tree');
+ });
+
+ it('filters content automatically for Android requests', async () => {
+ const res = await requester(null, { 'x-client': 'habitica-android' }).get('/content');
+ expect(res).to.not.have.nested.property('appearances.background.beach');
+ expect(res).to.have.nested.property('backgrounds.backgrounds062014');
+ expect(res).to.not.have.property('backgroundsFlat');
+ expect(res).to.not.have.nested.property('gear.tree');
+ });
+
+ it('filters content if the request specifies a filter', async () => {
+ const res = await requester().get('/content?filter=backgroundsFlat,gear.flat');
+ expect(res).to.not.have.property('backgroundsFlat');
+ expect(res).to.have.nested.property('gear.tree');
+ expect(res).to.not.have.nested.property('gear.flat');
+ });
+
+ it('filters content if the request contains invalid filters', async () => {
+ const res = await requester().get('/content?filter=backgroundsFlat,invalid');
+ expect(res).to.not.have.property('backgroundsFlat');
+ });
});
diff --git a/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js
new file mode 100644
index 0000000000..c2bcc3b87b
--- /dev/null
+++ b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js
@@ -0,0 +1,46 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /debug/time-travel-time', () => {
+ let user;
+ let nconfStub;
+
+ before(async () => {
+ user = await generateUser({ permissions: { fullAccess: true } });
+ });
+
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ });
+
+ afterEach(() => {
+ nconfStub.restore();
+ });
+
+ it('returns the shifted time', async () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
+ const result = await user.get('/debug/time-travel-time');
+ expect(result.time).to.exist;
+ await user.post('/debug/jump-time', { disable: true });
+ });
+
+ it('returns shifted when the user is not an admin', async () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
+ const regularUser = await generateUser();
+ const result = await regularUser.get('/debug/time-travel-time');
+ expect(result.time).to.exist;
+ });
+
+ it('returns error when not in time travel mode', async () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
+
+ await expect(user.get('/debug/time-travel-time'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js
index b0ad288348..6d7650e07a 100644
--- a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js
+++ b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js
@@ -5,16 +5,23 @@ import {
describe('POST /debug/add-hourglass', () => {
let userToGetHourGlass;
+ let nconfStub;
before(async () => {
userToGetHourGlass = await generateUser();
});
- after(() => {
- nconf.set('IS_PROD', false);
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
+ afterEach(() => {
+ nconfStub.restore();
});
it('adds Hourglass to the current user', async () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
await userToGetHourGlass.post('/debug/add-hourglass');
const userWithHourGlass = await userToGetHourGlass.get('/user');
@@ -23,7 +30,7 @@ describe('POST /debug/add-hourglass', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(userToGetHourGlass.post('/debug/add-hourglass'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js
index 74c39a153f..560381b4b0 100644
--- a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js
+++ b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js
@@ -5,16 +5,23 @@ import {
describe('POST /debug/add-ten-gems', () => {
let userToGainTenGems;
+ let nconfStub;
before(async () => {
userToGainTenGems = await generateUser();
});
- after(() => {
- nconf.set('IS_PROD', false);
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
+ afterEach(() => {
+ nconfStub.restore();
});
it('adds ten gems to the current user', async () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
await userToGainTenGems.post('/debug/add-ten-gems');
const userWithTenGems = await userToGainTenGems.get('/user');
@@ -23,7 +30,7 @@ describe('POST /debug/add-ten-gems', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(userToGainTenGems.post('/debug/add-ten-gems'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/debug/POST-debug_jumpTime.test.js b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js
new file mode 100644
index 0000000000..68988f682d
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js
@@ -0,0 +1,82 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /debug/jump-time', () => {
+ let user;
+ let today;
+ let nconfStub;
+
+ before(async () => {
+ user = await generateUser({ permissions: { fullAccess: true } });
+ today = new Date();
+ });
+
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
+ });
+
+ afterEach(() => {
+ nconfStub.restore();
+ });
+
+ after(async () => {
+ nconf.set('TIME_TRAVEL_ENABLED', true);
+ await user.post('/debug/jump-time', { disable: true });
+ nconf.set('TIME_TRAVEL_ENABLED', false);
+ });
+
+ it('Jumps forward', async () => {
+ const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
+ expect(resultDate.getDate()).to.eql(today.getDate());
+ expect(resultDate.getMonth()).to.eql(today.getMonth());
+ expect(resultDate.getFullYear()).to.eql(today.getFullYear());
+ const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time);
+ expect(newResultDate.getDate()).to.eql(today.getDate() + 1);
+ expect(newResultDate.getMonth()).to.eql(today.getMonth());
+ expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
+ });
+
+ it('jumps back', async () => {
+ const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
+ expect(resultDate.getDate()).to.eql(today.getDate());
+ expect(resultDate.getMonth()).to.eql(today.getMonth());
+ expect(resultDate.getFullYear()).to.eql(today.getFullYear());
+ const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time);
+ expect(newResultDate.getDate()).to.eql(today.getDate() - 1);
+ expect(newResultDate.getMonth()).to.eql(today.getMonth());
+ expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
+ });
+
+ it('can jump a lot', async () => {
+ const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
+ expect(resultDate.getDate()).to.eql(today.getDate());
+ expect(resultDate.getMonth()).to.eql(today.getMonth());
+ expect(resultDate.getFullYear()).to.eql(today.getFullYear());
+ const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
+ expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
+ });
+
+ it('returns error when the user is not an admin', async () => {
+ const regularUser = await generateUser();
+ await expect(regularUser.post('/debug/jump-time', { offsetDays: 1 }))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 400,
+ error: 'BadRequest',
+ message: 'You do not have permission to time travel.',
+ });
+ });
+
+ it('returns error when not in time travel mode', async () => {
+ nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
+
+ await expect(user.post('/debug/jump-time', { offsetDays: 1 }))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
index c03fa4627f..dcbcc2685b 100644
--- a/test/api/v3/integration/debug/POST-debug_make-admin.test.js
+++ b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
@@ -5,16 +5,23 @@ import {
describe('POST /debug/make-admin', () => {
let user;
+ let nconfStub;
before(async () => {
user = await generateUser();
});
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
afterEach(() => {
- nconf.set('IS_PROD', false);
+ nconfStub.restore();
});
it('makes user an admin', async () => {
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
await user.post('/debug/make-admin');
await user.sync();
@@ -23,7 +30,7 @@ describe('POST /debug/make-admin', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(user.post('/debug/make-admin'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js
index 8b9f6ac10d..1cd8e368b1 100644
--- a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js
+++ b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js
@@ -8,6 +8,7 @@ import {
describe('POST /debug/modify-inventory', () => {
let user; let
originalItems;
+ let nconfStub;
before(async () => {
originalItems = {
@@ -39,8 +40,14 @@ describe('POST /debug/modify-inventory', () => {
});
});
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
afterEach(() => {
- nconf.set('IS_PROD', false);
+ nconfStub.restore();
});
it('sets equipment', async () => {
@@ -149,7 +156,7 @@ describe('POST /debug/modify-inventory', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(user.post('/debug/modify-inventory'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js
index c74f3bf6af..a0bc27f379 100644
--- a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js
+++ b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js
@@ -5,13 +5,20 @@ import {
describe('POST /debug/quest-progress', () => {
let user;
+ let nconfStub;
beforeEach(async () => {
user = await generateUser();
});
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
afterEach(() => {
- nconf.set('IS_PROD', false);
+ nconfStub.restore();
});
it('errors if user is not on a quest', async () => {
@@ -48,7 +55,7 @@ describe('POST /debug/quest-progress', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(user.post('/debug/quest-progress'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/debug/POST-debug_set-cron.test.js b/test/api/v3/integration/debug/POST-debug_set-cron.test.js
index dfcd99c625..671160e552 100644
--- a/test/api/v3/integration/debug/POST-debug_set-cron.test.js
+++ b/test/api/v3/integration/debug/POST-debug_set-cron.test.js
@@ -5,13 +5,20 @@ import {
describe('POST /debug/set-cron', () => {
let user;
+ let nconfStub;
before(async () => {
user = await generateUser();
});
+ beforeEach(() => {
+ nconfStub = sandbox.stub(nconf, 'get');
+ nconfStub.withArgs('DEBUG_ENABLED').returns(true);
+ nconfStub.withArgs('BASE_URL').returns('https://example.com');
+ });
+
afterEach(() => {
- nconf.set('IS_PROD', false);
+ nconfStub.restore();
});
it('sets last cron', async () => {
@@ -27,7 +34,7 @@ describe('POST /debug/set-cron', () => {
});
it('returns error when not in production mode', async () => {
- nconf.set('IS_PROD', true);
+ nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(user.post('/debug/set-cron'))
.eventually.be.rejected.and.to.deep.equal({
diff --git a/test/api/v3/integration/shops/GET-shops_backgrounds.test.js b/test/api/v3/integration/shops/GET-shops_backgrounds.test.js
index 387a3bf40f..ec38a83460 100644
--- a/test/api/v3/integration/shops/GET-shops_backgrounds.test.js
+++ b/test/api/v3/integration/shops/GET-shops_backgrounds.test.js
@@ -17,9 +17,5 @@ describe('GET /shops/backgrounds', () => {
expect(shop.notes).to.eql(t('backgroundShop'));
expect(shop.imageName).to.equal('background_shop');
expect(shop.sets).to.be.an('array');
-
- const sets = shop.sets.map(set => set.identifier);
- expect(sets).to.include('incentiveBackgrounds');
- expect(sets).to.include('backgrounds062014');
});
});
diff --git a/test/api/v3/integration/shops/GET-shops_time_travelers.test.js b/test/api/v3/integration/shops/GET-shops_time_travelers.test.js
index 3e81459eac..cfe384bf1c 100644
--- a/test/api/v3/integration/shops/GET-shops_time_travelers.test.js
+++ b/test/api/v3/integration/shops/GET-shops_time_travelers.test.js
@@ -5,9 +5,15 @@ import {
describe('GET /shops/time-travelers', () => {
let user;
+ let clock;
beforeEach(async () => {
user = await generateUser();
+ clock = sinon.useFakeTimers(new Date('2024-06-08'));
+ });
+
+ afterEach(() => {
+ clock.restore();
});
it('returns a valid shop object', async () => {
diff --git a/test/api/v3/integration/user/POST-user_purchase.test.js b/test/api/v3/integration/user/POST-user_purchase.test.js
index c21692bc4c..0763913717 100644
--- a/test/api/v3/integration/user/POST-user_purchase.test.js
+++ b/test/api/v3/integration/user/POST-user_purchase.test.js
@@ -33,6 +33,20 @@ describe('POST /user/purchase/:type/:key', () => {
expect(user.items[type][key]).to.equal(1);
});
+ it('purchases animal ears', async () => {
+ await user.post('/user/purchase/gear/headAccessory_special_tigerEars');
+ await user.sync();
+
+ expect(user.items.gear.owned.headAccessory_special_tigerEars).to.equal(true);
+ });
+
+ it('purchases animal tails', async () => {
+ await user.post('/user/purchase/gear/back_special_pandaTail');
+ await user.sync();
+
+ expect(user.items.gear.owned.back_special_pandaTail).to.equal(true);
+ });
+
it('can convert gold to gems if subscribed', async () => {
const oldBalance = user.balance;
await user.updateOne({
diff --git a/test/api/v3/integration/user/POST-user_unlock.js b/test/api/v3/integration/user/POST-user_unlock.js
index 26016ef275..eeae6ceb36 100644
--- a/test/api/v3/integration/user/POST-user_unlock.js
+++ b/test/api/v3/integration/user/POST-user_unlock.js
@@ -5,7 +5,7 @@ import {
describe('POST /user/unlock', () => {
let user;
- const unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
+ const unlockPath = 'shirt.convict,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars';
const unlockCost = 1.25;
const usersStartingGems = 5;
diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js
index 49f365465a..6f1b2b5bba 100644
--- a/test/api/v3/integration/user/PUT-user.test.js
+++ b/test/api/v3/integration/user/PUT-user.test.js
@@ -274,6 +274,14 @@ describe('PUT /user', () => {
expect(get(updatedUser.preferences, type)).to.eql(item);
});
});
+
+ it('updates user when background is unequipped', async () => {
+ expect(get(user.preferences, 'background')).to.not.eql('');
+
+ const updatedUser = await user.put('/user', { 'preferences.background': '' });
+
+ expect(get(updatedUser.preferences, 'background')).to.eql('');
+ });
});
context('Improvement Categories', () => {
diff --git a/test/api/v3/integration/user/buy/POST-user_buy.test.js b/test/api/v3/integration/user/buy/POST-user_buy.test.js
index f91831d37a..4590d7bc62 100644
--- a/test/api/v3/integration/user/buy/POST-user_buy.test.js
+++ b/test/api/v3/integration/user/buy/POST-user_buy.test.js
@@ -11,6 +11,7 @@ const { content } = shared;
describe('POST /user/buy/:key', () => {
let user;
+ let clock;
beforeEach(async () => {
user = await generateUser({
@@ -18,6 +19,12 @@ describe('POST /user/buy/:key', () => {
});
});
+ afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ });
+
// More tests in common code unit tests
it('returns an error if the item is not found', async () => {
@@ -68,9 +75,9 @@ describe('POST /user/buy/:key', () => {
});
it('buys a special spell', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-10-31T00:00:00Z'));
const key = 'spookySparkles';
const item = content.special[key];
- const stub = sinon.stub(item, 'canOwn').returns(true);
await user.updateOne({ 'stats.gp': 250 });
const res = await user.post(`/user/buy/${key}`);
@@ -83,8 +90,6 @@ describe('POST /user/buy/:key', () => {
expect(res.message).to.equal(t('messageBought', {
itemText: item.text(),
}));
-
- stub.restore();
});
it('allows for bulk purchases', async () => {
diff --git a/test/api/v3/integration/world-state/GET-world-state.test.js b/test/api/v3/integration/world-state/GET-world-state.test.js
index cec7086d6f..6c33296921 100644
--- a/test/api/v3/integration/world-state/GET-world-state.test.js
+++ b/test/api/v3/integration/world-state/GET-world-state.test.js
@@ -1,5 +1,3 @@
-import { TAVERN_ID } from '../../../../../website/server/models/group';
-import { updateDocument } from '../../../../helpers/mongo';
import {
requester,
resetHabiticaDB,
@@ -18,7 +16,9 @@ describe('GET /world-state', () => {
});
it('returns Tavern quest data when world boss is active', async () => {
- await updateDocument('groups', { _id: TAVERN_ID }, { quest: { active: true, key: 'dysheartener', progress: { hp: 50000, rage: 9999 } } });
+ sinon.stub(worldState, 'getWorldBoss').returns({
+ active: true, extra: {}, key: 'dysheartener', progress: { hp: 50000, rage: 9999, collect: {} },
+ });
const res = await requester().get('/world-state');
expect(res).to.have.nested.property('worldBoss');
@@ -33,15 +33,29 @@ describe('GET /world-state', () => {
rage: 9999,
},
});
+ worldState.getWorldBoss.restore();
+ });
+
+ it('calls getRepeatingEvents for data', async () => {
+ const getRepeatingEventsOnDate = sinon.stub(common.content, 'getRepeatingEventsOnDate').returns([]);
+ const getCurrentGalaEvent = sinon.stub(common.schedule, 'getCurrentGalaEvent').returns({});
+
+ await requester().get('/world-state');
+
+ expect(getRepeatingEventsOnDate).to.have.been.calledOnce;
+ expect(getCurrentGalaEvent).to.have.been.calledOnce;
+
+ getRepeatingEventsOnDate.restore();
+ getCurrentGalaEvent.restore();
});
context('no current event', () => {
beforeEach(async () => {
- sinon.stub(worldState, 'getCurrentEvent').returns(null);
+ sinon.stub(worldState, 'getCurrentEventList').returns([]);
});
afterEach(() => {
- worldState.getCurrentEvent.restore();
+ worldState.getCurrentEventList.restore();
});
it('returns null for the current event when there is none active', async () => {
@@ -51,18 +65,18 @@ describe('GET /world-state', () => {
});
});
- context('no current event', () => {
+ context('active event', () => {
const evt = {
...common.content.events.fall2020,
event: 'fall2020',
};
beforeEach(async () => {
- sinon.stub(worldState, 'getCurrentEvent').returns(evt);
+ sinon.stub(worldState, 'getCurrentEventList').returns([evt]);
});
afterEach(() => {
- worldState.getCurrentEvent.restore();
+ worldState.getCurrentEventList.restore();
});
it('returns the current event when there is an active one', async () => {
@@ -71,4 +85,45 @@ describe('GET /world-state', () => {
expect(res.currentEvent).to.eql(evt);
});
});
+
+ context('active event with NPC image suffix', () => {
+ const evt = {
+ ...common.content.events.fall2020,
+ event: 'fall2020',
+ npcImageSuffix: 'fall',
+ };
+
+ beforeEach(async () => {
+ sinon.stub(worldState, 'getCurrentEventList').returns([evt]);
+ });
+
+ afterEach(() => {
+ worldState.getCurrentEventList.restore();
+ });
+
+ it('returns the NPC image suffix when present', async () => {
+ const res = await requester().get('/world-state');
+
+ expect(res.npcImageSuffix).to.equal('fall');
+ });
+
+ it('returns the NPC image suffix with multiple events present', async () => {
+ const evt2 = {
+ ...common.content.events.winter2020,
+ event: 'test',
+ };
+
+ const evt3 = {
+ ...common.content.events.winter2020,
+ event: 'winter2020',
+ npcImageSuffix: 'winter',
+ };
+
+ worldState.getCurrentEventList.returns([evt, evt2, evt3]);
+
+ const res = await requester().get('/world-state');
+
+ expect(res.npcImageSuffix).to.equal('winter');
+ });
+ });
});
diff --git a/test/common/fns/datedMemoize.test.js b/test/common/fns/datedMemoize.test.js
new file mode 100644
index 0000000000..1e32eb3888
--- /dev/null
+++ b/test/common/fns/datedMemoize.test.js
@@ -0,0 +1,67 @@
+/* eslint-disable global-require */
+import { expect } from 'chai';
+import nconf from 'nconf';
+
+const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
+
+describe('datedMemoize', () => {
+ it('should return a function that returns a function', () => {
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(() => {});
+ expect(memoized).to.be.a('function');
+ });
+
+ it('should not call multiple times', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized(1, 2);
+ memoized(1, 3);
+ expect(stub).to.have.been.calledOnce;
+ });
+
+ it('call multiple times for different identifiers', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized({ identifier: 'a', memoizeConfig: true }, 1, 2);
+ memoized({ identifier: 'b', memoizeConfig: true }, 1, 2);
+ expect(stub).to.have.been.calledTwice;
+ });
+
+ it('call once for the same identifier', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized({ identifier: 'a', memoizeConfig: true }, 1, 2);
+ memoized({ identifier: 'a', memoizeConfig: true }, 1, 2);
+ expect(stub).to.have.been.calledOnce;
+ });
+
+ it('call once on the same day', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized({ date: new Date('2024-01-01'), memoizeConfig: true }, 1, 2);
+ memoized({ date: new Date('2024-01-01'), memoizeConfig: true }, 1, 2);
+ expect(stub).to.have.been.calledOnce;
+ });
+
+ it('call multiple times on different days', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized({ date: new Date('2024-01-01'), memoizeConfig: true }, 1, 2);
+ memoized({ date: new Date('2024-01-02'), memoizeConfig: true }, 1, 2);
+ expect(stub).to.have.been.calledTwice;
+ });
+
+ it('respects switchover time', () => {
+ const stub = sandbox.stub().returns({});
+ const datedMemoize = require('../../../website/common/script/fns/datedMemoize').default;
+ const memoized = datedMemoize(stub);
+ memoized({ date: new Date('2024-01-01T00:00:00.000Z'), memoizeConfig: true }, 1, 2);
+ memoized({ date: new Date(`2024-01-01T${String(SWITCHOVER_TIME).padStart(2, '0')}`), memoizeConfig: true }, 1, 2);
+ expect(stub).to.have.been.calledTwice;
+ });
+});
diff --git a/test/common/libs/cleanupPinnedItems.test.js b/test/common/libs/cleanupPinnedItems.test.js
new file mode 100644
index 0000000000..1c1cb2ee43
--- /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('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/test/common/libs/shops.js b/test/common/libs/shops.js
deleted file mode 100644
index 54ed48dc54..0000000000
--- a/test/common/libs/shops.js
+++ /dev/null
@@ -1,219 +0,0 @@
-import shared from '../../../website/common';
-import {
- generateUser,
-} from '../../helpers/common.helper';
-
-describe('shops', () => {
- const user = generateUser();
-
- describe('market', () => {
- const shopCategories = shared.shops.getMarketCategories(user);
-
- it('contains at least the 3 default categories', () => {
- expect(shopCategories.length).to.be.greaterThan(2);
- });
-
- it('does not contain an empty category', () => {
- _.each(shopCategories, category => {
- expect(category.items.length).to.be.greaterThan(0);
- });
- });
-
- it('does not duplicate identifiers', () => {
- const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
-
- expect(identifiers.length).to.eql(shopCategories.length);
- });
-
- it('items contain required fields', () => {
- _.each(shopCategories, category => {
- _.each(category.items, item => {
- _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'class'], key => {
- expect(_.has(item, key)).to.eql(true);
- });
- });
- });
- });
-
- it('shows relevant non class gear in special category', () => {
- const contributor = generateUser({
- contributor: {
- level: 7,
- critical: true,
- },
- items: {
- gear: {
- owned: {
- weapon_armoire_basicCrossbow: true, // eslint-disable-line camelcase
- },
- },
- },
- });
-
- const gearCategories = shared.shops.getMarketGearCategories(contributor);
- const specialCategory = gearCategories.find(o => o.identifier === 'none');
- expect(specialCategory.items.find(item => item.key === 'weapon_special_1'));
- expect(specialCategory.items.find(item => item.key === 'armor_special_1'));
- expect(specialCategory.items.find(item => item.key === 'head_special_1'));
- expect(specialCategory.items.find(item => item.key === 'shield_special_1'));
- expect(specialCategory.items.find(item => item.key === 'weapon_special_critical'));
- expect(specialCategory.items.find(item => item.key === 'weapon_armoire_basicCrossbow'));// eslint-disable-line camelcase
- });
-
- it('does not show gear when it is all owned', () => {
- const userWithItems = generateUser({
- stats: {
- class: 'wizard',
- },
- items: {
- gear: {
- owned: {
- weapon_wizard_0: true, // eslint-disable-line camelcase
- weapon_wizard_1: true, // eslint-disable-line camelcase
- weapon_wizard_2: true, // eslint-disable-line camelcase
- weapon_wizard_3: true, // eslint-disable-line camelcase
- weapon_wizard_4: true, // eslint-disable-line camelcase
- weapon_wizard_5: true, // eslint-disable-line camelcase
- weapon_wizard_6: true, // eslint-disable-line camelcase
- armor_wizard_1: true, // eslint-disable-line camelcase
- armor_wizard_2: true, // eslint-disable-line camelcase
- armor_wizard_3: true, // eslint-disable-line camelcase
- armor_wizard_4: true, // eslint-disable-line camelcase
- armor_wizard_5: true, // eslint-disable-line camelcase
- head_wizard_1: true, // eslint-disable-line camelcase
- head_wizard_2: true, // eslint-disable-line camelcase
- head_wizard_3: true, // eslint-disable-line camelcase
- head_wizard_4: true, // eslint-disable-line camelcase
- head_wizard_5: true, // eslint-disable-line camelcase
- },
- },
- },
- });
-
- const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
- expect(shopWizardItems.length).to.eql(0);
- });
-
- it('shows available gear not yet purchased and previously owned', () => {
- const userWithItems = generateUser({
- stats: {
- class: 'wizard',
- },
- items: {
- gear: {
- owned: {
- weapon_wizard_0: true, // eslint-disable-line camelcase
- weapon_wizard_1: true, // eslint-disable-line camelcase
- weapon_wizard_2: true, // eslint-disable-line camelcase
- weapon_wizard_3: true, // eslint-disable-line camelcase
- weapon_wizard_4: true, // eslint-disable-line camelcase
- armor_wizard_1: true, // eslint-disable-line camelcase
- armor_wizard_2: true, // eslint-disable-line camelcase
- armor_wizard_3: false, // eslint-disable-line camelcase
- armor_wizard_4: false, // eslint-disable-line camelcase
- head_wizard_1: true, // eslint-disable-line camelcase
- head_wizard_2: false, // eslint-disable-line camelcase
- head_wizard_3: true, // eslint-disable-line camelcase
- head_wizard_4: false, // eslint-disable-line camelcase
- head_wizard_5: true, // eslint-disable-line camelcase
- },
- },
- },
- });
-
- const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
- expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
- expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);
- expect(shopWizardItems.find(item => item.key === 'armor_wizard_3').locked).to.eql(false);
- expect(shopWizardItems.find(item => item.key === 'armor_wizard_4').locked).to.eql(true);
- expect(shopWizardItems.find(item => item.key === 'head_wizard_2').locked).to.eql(false);
- expect(shopWizardItems.find(item => item.key === 'head_wizard_4').locked).to.eql(true);
- });
- });
-
- describe('questShop', () => {
- const shopCategories = shared.shops.getQuestShopCategories(user);
-
- it('does not contain an empty category', () => {
- _.each(shopCategories, category => {
- expect(category.items.length).to.be.greaterThan(0);
- });
- });
-
- it('does not duplicate identifiers', () => {
- const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
-
- expect(identifiers.length).to.eql(shopCategories.length);
- });
-
- it('items contain required fields', () => {
- _.each(shopCategories, category => {
- if (category.identifier === 'bundle') {
- _.each(category.items, item => {
- _.each(['key', 'text', 'notes', 'value', 'currency', 'purchaseType', 'class'], key => {
- expect(_.has(item, key)).to.eql(true);
- });
- });
- } else {
- _.each(category.items, item => {
- _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'boss', 'class', 'collect', 'drop', 'unlockCondition', 'lvl'], key => {
- expect(_.has(item, key)).to.eql(true);
- });
- });
- }
- });
- });
- });
-
- describe('timeTravelers', () => {
- const shopCategories = shared.shops.getTimeTravelersCategories(user);
-
- it('does not contain an empty category', () => {
- _.each(shopCategories, category => {
- expect(category.items.length).to.be.greaterThan(0);
- });
- });
-
- it('does not duplicate identifiers', () => {
- const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
-
- expect(identifiers.length).to.eql(shopCategories.length);
- });
-
- it('items contain required fields', () => {
- _.each(shopCategories, category => {
- _.each(category.items, item => {
- _.each(['key', 'text', 'value', 'currency', 'locked', 'purchaseType', 'class', 'notes', 'class'], key => {
- expect(_.has(item, key)).to.eql(true);
- });
- });
- });
- });
- });
-
- describe('seasonalShop', () => {
- const shopCategories = shared.shops.getSeasonalShopCategories(user);
-
- it('does not contain an empty category', () => {
- _.each(shopCategories, category => {
- expect(category.items.length).to.be.greaterThan(0);
- });
- });
-
- it('does not duplicate identifiers', () => {
- const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
-
- expect(identifiers.length).to.eql(shopCategories.length);
- });
-
- it('items contain required fields', () => {
- _.each(shopCategories, category => {
- _.each(category.items, item => {
- _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'type'], key => {
- expect(_.has(item, key)).to.eql(true);
- });
- });
- });
- });
- });
-});
diff --git a/test/common/libs/shops.test.js b/test/common/libs/shops.test.js
new file mode 100644
index 0000000000..576d31c32d
--- /dev/null
+++ b/test/common/libs/shops.test.js
@@ -0,0 +1,430 @@
+import shared from '../../../website/common';
+import {
+ generateUser,
+} from '../../helpers/common.helper';
+
+import seasonalConfig from '../../../website/common/script/libs/shops-seasonal.config';
+
+describe('shops', () => {
+ const user = generateUser();
+ let clock;
+
+ afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ user.achievements.quests = {};
+ });
+
+ describe('market', () => {
+ const shopCategories = shared.shops.getMarketCategories(user);
+
+ it('contains at least the 3 default categories', () => {
+ expect(shopCategories.length).to.be.greaterThan(2);
+ });
+
+ it('does not contain an empty category', () => {
+ _.each(shopCategories, category => {
+ expect(category.items.length).to.be.greaterThan(0);
+ });
+ });
+
+ it('does not duplicate identifiers', () => {
+ const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
+
+ expect(identifiers.length).to.eql(shopCategories.length);
+ });
+
+ it('items contain required fields', () => {
+ _.each(shopCategories, category => {
+ _.each(category.items, item => {
+ _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'class'], key => {
+ expect(_.has(item, key)).to.eql(true);
+ });
+ });
+ });
+ });
+
+ describe('premium hatching potions', () => {
+ it('contains current scheduled premium hatching potions', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-04-01'));
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ expect(potions.items.length).to.eql(2);
+ });
+
+ it('does not contain past scheduled premium hatching potions', async () => {
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ expect(potions.items.filter(x => x.key === 'Aquatic' || x.key === 'Celestial').length).to.eql(0);
+ });
+ it('returns end date for scheduled premium potions', async () => {
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ potions.items.forEach(potion => {
+ expect(potion.end).to.exist;
+ });
+ });
+
+ it('contains unlocked quest premium hatching potions', async () => {
+ user.achievements.quests = {
+ bronze: 1,
+ blackPearl: 1,
+ };
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(2);
+ });
+
+ it('does not contain locked quest premium hatching potions', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-04-01'));
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ expect(potions.items.length).to.eql(2);
+ expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(0);
+ });
+
+ it('does not return end date for quest premium potions', async () => {
+ user.achievements.quests = {
+ bronze: 1,
+ blackPearl: 1,
+ };
+ const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
+ potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').forEach(potion => {
+ expect(potion.end).to.not.exist;
+ });
+ });
+ });
+
+ it('does not return items with event data', async () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.event).to.not.exist;
+ expect(item.season).to.not.exist;
+ });
+ });
+ });
+
+ it('shows relevant non class gear in special category', () => {
+ const contributor = generateUser({
+ contributor: {
+ level: 7,
+ critical: true,
+ },
+ items: {
+ gear: {
+ owned: {
+ weapon_armoire_basicCrossbow: true, // eslint-disable-line camelcase
+ },
+ },
+ },
+ });
+
+ const gearCategories = shared.shops.getMarketGearCategories(contributor);
+ const specialCategory = gearCategories.find(o => o.identifier === 'none');
+ expect(specialCategory.items.find(item => item.key === 'weapon_special_1'), 'weapon_special_1');
+ expect(specialCategory.items.find(item => item.key === 'armor_special_1'), 'armor_special_1');
+ expect(specialCategory.items.find(item => item.key === 'head_special_1'), 'head_special_1');
+ expect(specialCategory.items.find(item => item.key === 'shield_special_1'), 'shield_special_1');
+ expect(specialCategory.items.find(item => item.key === 'weapon_special_critical'), 'weapon_special_critical');
+ expect(specialCategory.items.find(item => item.key === 'weapon_armoire_basicCrossbow'), 'weapon_armoire_basicCrossbow');// eslint-disable-line camelcase
+ });
+
+ describe('handles seasonal gear', () => {
+ beforeEach(() => {
+ clock = sinon.useFakeTimers(new Date('2024-04-01'));
+ });
+
+ it('shows current seasonal gear for warriors', () => {
+ const warriorItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'warrior').items.filter(x => x.key.indexOf('spring2024') !== -1);
+ expect(warriorItems.length, 'Warrior seasonal gear').to.eql(4);
+ });
+
+ it('shows current seasonal gear for mages', () => {
+ const mageItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'wizard').items.filter(x => x.key.indexOf('spring2024') !== -1);
+ expect(mageItems.length, 'Mage seasonal gear').to.eql(3);
+ });
+
+ it('shows current seasonal gear for healers', () => {
+ const healerItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'healer').items.filter(x => x.key.indexOf('spring2024') !== -1);
+ expect(healerItems.length, 'Healer seasonal gear').to.eql(4);
+ });
+
+ it('shows current seasonal gear for rogues', () => {
+ const rogueItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'rogue').items.filter(x => x.key.indexOf('spring2024') !== -1);
+ expect(rogueItems.length, 'Rogue seasonal gear').to.eql(4);
+ });
+
+ it('seasonal gear contains end date', () => {
+ const categories = shared.shops.getMarketGearCategories(user);
+ categories.forEach(category => {
+ category.items.filter(x => x.key.indexOf('spring2024') !== -1).forEach(item => {
+ expect(item.end, item.key).to.exist;
+ });
+ });
+ });
+
+ it('only shows gear for the current season', () => {
+ const categories = shared.shops.getMarketGearCategories(user);
+ categories.forEach(category => {
+ const otherSeasons = category.items.filter(item => item.key.indexOf('winter') !== -1 || item.key.indexOf('summer') !== -1 || item.key.indexOf('fall') !== -1);
+ expect(otherSeasons.length).to.eql(0);
+ });
+ });
+
+ it('does not show gear from past seasons', () => {
+ const categories = shared.shops.getMarketGearCategories(user);
+ categories.forEach(category => {
+ const otherYears = category.items.filter(item => item.key.indexOf('spring') !== -1 && item.key.indexOf('2024') === -1);
+ expect(otherYears.length).to.eql(0);
+ });
+ });
+ });
+
+ it('does not show gear when it is all owned', () => {
+ const userWithItems = generateUser({
+ stats: {
+ class: 'wizard',
+ },
+ items: {
+ gear: {
+ owned: {
+ weapon_wizard_0: true, // eslint-disable-line camelcase
+ weapon_wizard_1: true, // eslint-disable-line camelcase
+ weapon_wizard_2: true, // eslint-disable-line camelcase
+ weapon_wizard_3: true, // eslint-disable-line camelcase
+ weapon_wizard_4: true, // eslint-disable-line camelcase
+ weapon_wizard_5: true, // eslint-disable-line camelcase
+ weapon_wizard_6: true, // eslint-disable-line camelcase
+ armor_wizard_1: true, // eslint-disable-line camelcase
+ armor_wizard_2: true, // eslint-disable-line camelcase
+ armor_wizard_3: true, // eslint-disable-line camelcase
+ armor_wizard_4: true, // eslint-disable-line camelcase
+ armor_wizard_5: true, // eslint-disable-line camelcase
+ head_wizard_1: true, // eslint-disable-line camelcase
+ head_wizard_2: true, // eslint-disable-line camelcase
+ head_wizard_3: true, // eslint-disable-line camelcase
+ head_wizard_4: true, // eslint-disable-line camelcase
+ head_wizard_5: true, // eslint-disable-line camelcase
+ },
+ },
+ },
+ });
+
+ const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
+ expect(shopWizardItems.length).to.eql(0);
+ });
+
+ it('shows available gear not yet purchased and previously owned', () => {
+ const userWithItems = generateUser({
+ stats: {
+ class: 'wizard',
+ },
+ items: {
+ gear: {
+ owned: {
+ weapon_wizard_0: true, // eslint-disable-line camelcase
+ weapon_wizard_1: true, // eslint-disable-line camelcase
+ weapon_wizard_2: true, // eslint-disable-line camelcase
+ weapon_wizard_3: true, // eslint-disable-line camelcase
+ weapon_wizard_4: true, // eslint-disable-line camelcase
+ armor_wizard_1: true, // eslint-disable-line camelcase
+ armor_wizard_2: true, // eslint-disable-line camelcase
+ armor_wizard_3: false, // eslint-disable-line camelcase
+ armor_wizard_4: false, // eslint-disable-line camelcase
+ head_wizard_1: true, // eslint-disable-line camelcase
+ head_wizard_2: false, // eslint-disable-line camelcase
+ head_wizard_3: true, // eslint-disable-line camelcase
+ head_wizard_4: false, // eslint-disable-line camelcase
+ head_wizard_5: true, // eslint-disable-line camelcase
+ },
+ },
+ },
+ });
+
+ const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
+ expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
+ expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);
+ expect(shopWizardItems.find(item => item.key === 'armor_wizard_3').locked).to.eql(false);
+ expect(shopWizardItems.find(item => item.key === 'armor_wizard_4').locked).to.eql(true);
+ expect(shopWizardItems.find(item => item.key === 'head_wizard_2').locked).to.eql(false);
+ expect(shopWizardItems.find(item => item.key === 'head_wizard_4').locked).to.eql(true);
+ });
+ });
+
+ describe('questShop', () => {
+ const shopCategories = shared.shops.getQuestShopCategories(user);
+
+ it('does not contain an empty category', () => {
+ _.each(shopCategories, category => {
+ expect(category.items.length, category.identifier).to.be.greaterThan(0);
+ });
+ });
+
+ it('does not duplicate identifiers', () => {
+ const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
+
+ expect(identifiers.length).to.eql(shopCategories.length);
+ });
+
+ it('items contain required fields', () => {
+ _.each(shopCategories, category => {
+ if (category.identifier === 'bundle') {
+ _.each(category.items, item => {
+ _.each(['key', 'text', 'notes', 'value', 'currency', 'purchaseType', 'class'], key => {
+ expect(_.has(item, key)).to.eql(true);
+ });
+ });
+ } else {
+ _.each(category.items, item => {
+ _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'boss', 'class', 'collect', 'drop', 'unlockCondition', 'lvl'], key => {
+ expect(_.has(item, key)).to.eql(true);
+ });
+ });
+ }
+ });
+ });
+
+ it('does not return items with event data', async () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.event).to.not.exist;
+ });
+ });
+ });
+ });
+
+ describe('timeTravelers', () => {
+ const shopCategories = shared.shops.getTimeTravelersCategories(user);
+
+ it('does not contain an empty category', () => {
+ _.each(shopCategories, category => {
+ expect(category.items.length).to.be.greaterThan(0);
+ });
+ });
+
+ it('does not duplicate identifiers', () => {
+ const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
+
+ expect(identifiers.length).to.eql(shopCategories.length);
+ });
+
+ it('items contain required fields', () => {
+ _.each(shopCategories, category => {
+ _.each(category.items, item => {
+ _.each(['key', 'text', 'value', 'currency', 'locked', 'purchaseType', 'class', 'notes', 'class'], key => {
+ expect(_.has(item, key)).to.eql(true);
+ });
+ });
+ });
+ });
+
+ it('does not return items with event data', async () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.event).to.not.exist;
+ });
+ });
+ });
+
+ it('returns pets', () => {
+ const pets = shopCategories.find(cat => cat.identifier === 'pets').items;
+ expect(pets.length).to.be.greaterThan(0);
+ });
+
+ it('returns mounts', () => {
+ const mounts = shopCategories.find(cat => cat.identifier === 'mounts').items;
+ expect(mounts.length).to.be.greaterThan(0);
+ });
+
+ it('returns quests', () => {
+ const quests = shopCategories.find(cat => cat.identifier === 'quests').items;
+ expect(quests.length).to.be.greaterThan(0);
+ });
+
+ it('returns backgrounds', () => {
+ const backgrounds = shopCategories.find(cat => cat.identifier === 'backgrounds').items;
+ expect(backgrounds.length).to.be.greaterThan(0);
+ });
+ });
+
+ describe('customizationShop', () => {
+ const shopCategories = shared.shops.getCustomizationsShopCategories(user, null);
+
+ it('does not return items with event data', async () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.event, item.key).to.not.exist;
+ });
+ });
+ });
+
+ it('backgrounds category contains end date', () => {
+ const backgroundCategory = shopCategories.find(cat => cat.identifier === 'backgrounds');
+ expect(backgroundCategory.end).to.exist;
+ expect(backgroundCategory.end).to.be.greaterThan(new Date());
+ });
+
+ it('hair color category contains end date', () => {
+ const colorCategory = shopCategories.find(cat => cat.identifier === 'color');
+ expect(colorCategory.end).to.exist;
+ expect(colorCategory.end).to.be.greaterThan(new Date());
+ });
+
+ it('skin category contains end date', () => {
+ const colorCategory = shopCategories.find(cat => cat.identifier === 'color');
+ expect(colorCategory.end).to.exist;
+ expect(colorCategory.end).to.be.greaterThan(new Date());
+ });
+ });
+
+ describe('seasonalShop', () => {
+ const shopCategories = shared.shops.getSeasonalShopCategories(user, null, seasonalConfig());
+ const today = new Date();
+
+ it('does not contain an empty category', () => {
+ _.each(shopCategories, category => {
+ expect(category.items.length).to.be.greaterThan(0);
+ });
+ });
+
+ it('does not duplicate identifiers', () => {
+ const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
+
+ expect(identifiers.length).to.eql(shopCategories.length);
+ });
+
+ it('does not return items with event data', async () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.event, item.key).to.not.exist;
+ });
+ });
+ });
+
+ it('items contain required fields', () => {
+ _.each(shopCategories, category => {
+ _.each(category.items, item => {
+ _.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'type'], key => {
+ expect(_.has(item, key), item.key).to.eql(true);
+ });
+ });
+ });
+ });
+
+ it('items have a valid end date', () => {
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ expect(item.end, item.key).to.be.a('date');
+ expect(item.end, item.key).to.be.greaterThan(today);
+ });
+ });
+ });
+
+ it('items match current season', () => {
+ const currentSeason = seasonalConfig().currentSeason.toLowerCase();
+ shopCategories.forEach(category => {
+ category.items.forEach(item => {
+ if (item.klass === 'special') {
+ expect(item.season, item.key).to.eql(currentSeason);
+ }
+ });
+ });
+ });
+ });
+});
diff --git a/test/common/ops/armoireCanOwn.js b/test/common/ops/armoireCanOwn.js
index a0dd802aa0..a226c91df2 100644
--- a/test/common/ops/armoireCanOwn.js
+++ b/test/common/ops/armoireCanOwn.js
@@ -1,11 +1,10 @@
-import * as armoireSet from '../../../website/common/script/content/gear/sets/armoire';
+import armoireSet from '../../../website/common/script/content/gear/sets/armoire';
describe('armoireSet items', () => {
it('checks if canOwn has the same id', () => {
Object.keys(armoireSet).forEach(type => {
Object.keys(armoireSet[type]).forEach(itemKey => {
const ownedKey = `${type}_armoire_${itemKey}`;
-
expect(armoireSet[type][itemKey].canOwn({
items: {
gear: {
diff --git a/test/common/ops/buy/buy.js b/test/common/ops/buy/buy.test.js
similarity index 99%
rename from test/common/ops/buy/buy.js
rename to test/common/ops/buy/buy.test.js
index 2ac5fc6407..ff553f1aac 100644
--- a/test/common/ops/buy/buy.js
+++ b/test/common/ops/buy/buy.test.js
@@ -49,7 +49,7 @@ describe('shared.ops.buy', () => {
}
});
- it('recovers 15 hp', async () => {
+ it('buys health potion', async () => {
user.stats.hp = 30;
await buy(user, { params: { key: 'potion' } }, analytics);
expect(user.stats.hp).to.eql(45);
diff --git a/test/common/ops/buy/buyArmoire.js b/test/common/ops/buy/buyArmoire.test.js
similarity index 98%
rename from test/common/ops/buy/buyArmoire.js
rename to test/common/ops/buy/buyArmoire.test.js
index b1e1545f91..172625a9f6 100644
--- a/test/common/ops/buy/buyArmoire.js
+++ b/test/common/ops/buy/buyArmoire.test.js
@@ -17,9 +17,7 @@ function getFullArmoire () {
_.each(content.gearTypes, type => {
_.each(content.gear.tree[type].armoire, gearObject => {
- if (gearObject.released) {
- fullArmoire[gearObject.key] = true;
- }
+ fullArmoire[gearObject.key] = true;
});
});
diff --git a/test/common/ops/buy/buyGem.js b/test/common/ops/buy/buyGem.test.js
similarity index 100%
rename from test/common/ops/buy/buyGem.js
rename to test/common/ops/buy/buyGem.test.js
diff --git a/test/common/ops/buy/buyHealthPotion.js b/test/common/ops/buy/buyHealthPotion.test.js
similarity index 100%
rename from test/common/ops/buy/buyHealthPotion.js
rename to test/common/ops/buy/buyHealthPotion.test.js
diff --git a/test/common/ops/buy/buyMarketGear.js b/test/common/ops/buy/buyMarketGear.test.js
similarity index 81%
rename from test/common/ops/buy/buyMarketGear.js
rename to test/common/ops/buy/buyMarketGear.test.js
index 37725ff1b1..82d9b9ef53 100644
--- a/test/common/ops/buy/buyMarketGear.js
+++ b/test/common/ops/buy/buyMarketGear.test.js
@@ -22,6 +22,7 @@ async function buyGear (user, req, analytics) {
describe('shared.ops.buyMarketGear', () => {
let user;
const analytics = { track () {} };
+ let clock;
beforeEach(() => {
user = generateUser({
@@ -54,6 +55,10 @@ describe('shared.ops.buyMarketGear', () => {
shared.fns.predictableRandom.restore();
shared.onboarding.checkOnboardingStatus.restore();
analytics.track.restore();
+
+ if (clock) {
+ clock.restore();
+ }
});
context('Gear', () => {
@@ -184,30 +189,28 @@ describe('shared.ops.buyMarketGear', () => {
});
// TODO after user.ops.equip is done
- xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', async () => {
+ it('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', async () => {
user.stats.gp = 100;
user.preferences.autoEquip = true;
- await buyGear(user, { params: { key: 'shield_warrior_1' } });
- user.ops.equip({ params: { key: 'shield_warrior_1' } });
- await buyGear(user, { params: { key: 'weapon_warrior_1' } });
- user.ops.equip({ params: { key: 'weapon_warrior_1' } });
+ user.items.gear.equipped.weapon = 'weapon_warrior_1';
+ user.items.gear.equipped.shield = 'shield_warrior_1';
+ user.stats.class = 'wizard';
- await buyGear(user, { params: { key: 'weapon_wizard_1' } });
+ await buyGear(user, { params: { key: 'weapon_wizard_0' } });
expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0');
- expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1');
+ expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_0');
});
// TODO after user.ops.equip is done
- xit('buyGears two-handed equipment but does not automatically remove sword or shield', async () => {
+ it('buyGears two-handed equipment but does not automatically remove sword or shield', async () => {
user.stats.gp = 100;
user.preferences.autoEquip = false;
- await buyGear(user, { params: { key: 'shield_warrior_1' } });
- user.ops.equip({ params: { key: 'shield_warrior_1' } });
- await buyGear(user, { params: { key: 'weapon_warrior_1' } });
- user.ops.equip({ params: { key: 'weapon_warrior_1' } });
+ user.items.gear.equipped.weapon = 'weapon_warrior_1';
+ user.items.gear.equipped.shield = 'shield_warrior_1';
+ user.stats.class = 'wizard';
- await buyGear(user, { params: { key: 'weapon_wizard_1' } });
+ await buyGear(user, { params: { key: 'weapon_wizard_0' } });
expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1');
expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1');
@@ -283,5 +286,40 @@ describe('shared.ops.buyMarketGear', () => {
expect(user.items.gear.owned).to.have.property('shield_armoire_ramHornShield', true);
});
+
+ it('buys current seasonal gear', async () => {
+ user.stats.gp = 200;
+ clock = sinon.useFakeTimers(new Date('2024-01-01'));
+
+ await buyGear(user, { params: { key: 'armor_special_winter2024Warrior' } });
+
+ expect(user.items.gear.owned).to.have.property('armor_special_winter2024Warrior', true);
+ });
+
+ it('errors when buying past seasonal gear', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-01'));
+ user.stats.gp = 200;
+
+ try {
+ await buyGear(user, { params: { key: 'armor_special_winter2023Warrior' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ expect(user.items.gear.owned).to.not.have.property('armor_special_winter2023Warrior');
+ }
+ });
+
+ it('errors when buying gear from wrong season', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-01'));
+ user.stats.gp = 200;
+
+ try {
+ await buyGear(user, { params: { key: 'weapon_special_spring2024Warrior' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ expect(user.items.gear.owned).to.not.have.property('weapon_special_spring2024Warrior');
+ }
+ });
});
});
diff --git a/test/common/ops/buy/buyMysterySet.js b/test/common/ops/buy/buyMysterySet.test.js
similarity index 71%
rename from test/common/ops/buy/buyMysterySet.js
rename to test/common/ops/buy/buyMysterySet.test.js
index 1a75de3451..f2d74046ba 100644
--- a/test/common/ops/buy/buyMysterySet.js
+++ b/test/common/ops/buy/buyMysterySet.test.js
@@ -15,6 +15,7 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
describe('shared.ops.buyMysterySet', () => {
let user;
const analytics = { track () {} };
+ let clock;
beforeEach(() => {
user = generateUser({
@@ -31,6 +32,9 @@ describe('shared.ops.buyMysterySet', () => {
afterEach(() => {
analytics.track.restore();
+ if (clock) {
+ clock.restore();
+ }
});
context('Mystery Sets', () => {
@@ -41,7 +45,7 @@ describe('shared.ops.buyMysterySet', () => {
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('notEnoughHourglasses'));
- expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
+ expect(user.items.gear.owned).to.not.have.property('armor_mystery_201501');
}
});
@@ -72,6 +76,18 @@ describe('shared.ops.buyMysterySet', () => {
expect(err.message).to.equal(errorMessage('missingKeyParam'));
}
});
+
+ it('returns error if the set is not available', async () => {
+ user.purchased.plan.consecutive.trinkets = 1;
+ clock = sinon.useFakeTimers(new Date('2024-01-16'));
+ try {
+ await buyMysterySet(user, { params: { key: '201501' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.eql(i18n.t('notAvailable'));
+ expect(user.items.gear.owned).to.not.have.property('armor_mystery_201501');
+ }
+ });
});
context('successful purchases', () => {
@@ -86,6 +102,16 @@ describe('shared.ops.buyMysterySet', () => {
expect(user.items.gear.owned).to.have.property('head_mystery_301404', true);
expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true);
});
+
+ it('buys mystery set if it is available', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-16'));
+ user.purchased.plan.consecutive.trinkets = 1;
+ await buyMysterySet(user, { params: { key: '201601' } }, analytics);
+
+ expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
+ expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true);
+ expect(user.items.gear.owned).to.have.property('head_mystery_201601', true);
+ });
});
});
});
diff --git a/test/common/ops/buy/buyQuestGems.js b/test/common/ops/buy/buyQuestGems.test.js
similarity index 66%
rename from test/common/ops/buy/buyQuestGems.js
rename to test/common/ops/buy/buyQuestGems.test.js
index bb5346b420..aa5080a526 100644
--- a/test/common/ops/buy/buyQuestGems.js
+++ b/test/common/ops/buy/buyQuestGems.test.js
@@ -10,6 +10,7 @@ import { BuyQuestWithGemOperation } from '../../../../website/common/script/ops/
describe('shared.ops.buyQuestGems', () => {
let user;
+ let clock;
const goldPoints = 40;
const analytics = { track () {} };
@@ -26,11 +27,13 @@ describe('shared.ops.buyQuestGems', () => {
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
+ clock = sinon.useFakeTimers(new Date('2024-01-16'));
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
+ clock.restore();
});
context('single purchase', () => {
@@ -44,7 +47,7 @@ describe('shared.ops.buyQuestGems', () => {
user.pinnedItems.push({ type: 'quests', key: 'gryphon' });
});
- it('successfully purchases quest', async () => {
+ it('successfully purchases pet quest', async () => {
const key = 'gryphon';
await buyQuest(user, { params: { key } });
@@ -52,6 +55,61 @@ describe('shared.ops.buyQuestGems', () => {
expect(user.items.quests[key]).to.equal(1);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
+
+ it('successfully purchases hatching potion quest', async () => {
+ const key = 'silver';
+
+ await buyQuest(user, { params: { key } });
+
+ expect(user.items.quests[key]).to.equal(1);
+ expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
+ });
+
+ it('successfully purchases seasonal quest', async () => {
+ const key = 'evilsanta';
+
+ await buyQuest(user, { params: { key } });
+
+ expect(user.items.quests[key]).to.equal(1);
+ expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
+ });
+
+ it('errors if the pet quest is not available', async () => {
+ const key = 'sabretooth';
+
+ try {
+ await buyQuest(user, { params: { key } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ expect(user.items.quests[key]).to.eql(undefined);
+ }
+ });
+
+ it('errors if the hatching potion quest is not available', async () => {
+ const key = 'ruby';
+
+ try {
+ await buyQuest(user, { params: { key } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ expect(user.items.quests[key]).to.eql(undefined);
+ }
+ });
+
+ it('errors if the seasonal quest is not available', async () => {
+ const key = 'egg';
+
+ try {
+ await buyQuest(user, { params: { key } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ expect(user.items.quests[key]).to.eql(undefined);
+ }
+ });
+
it('if a user\'s count of a quest scroll is negative, it will be reset to 0 before incrementing when they buy a new one.', async () => {
const key = 'dustbunnies';
user.items.quests[key] = -1;
@@ -61,6 +119,7 @@ describe('shared.ops.buyQuestGems', () => {
expect(user.items.quests[key]).to.equal(1);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
+
it('errors if the user has not completed prerequisite quests', async () => {
const key = 'atom3';
user.achievements.quests.atom1 = 1;
@@ -73,6 +132,7 @@ describe('shared.ops.buyQuestGems', () => {
expect(user.items.quests[key]).to.eql(undefined);
}
});
+
it('successfully purchases quest if user has completed all prerequisite quests', async () => {
const key = 'atom3';
user.achievements.quests.atom1 = 1;
diff --git a/test/common/ops/buy/buyQuestGold.js b/test/common/ops/buy/buyQuestGold.test.js
similarity index 100%
rename from test/common/ops/buy/buyQuestGold.js
rename to test/common/ops/buy/buyQuestGold.test.js
diff --git a/test/common/ops/buy/buySpell.js b/test/common/ops/buy/buySpell.js
deleted file mode 100644
index f752e6c5a8..0000000000
--- a/test/common/ops/buy/buySpell.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { BuySpellOperation } from '../../../../website/common/script/ops/buy/buySpell';
-import {
- BadRequest,
- NotFound,
- NotAuthorized,
-} from '../../../../website/common/script/libs/errors';
-import i18n from '../../../../website/common/script/i18n';
-import {
- generateUser,
-} from '../../../helpers/common.helper';
-import content from '../../../../website/common/script/content/index';
-import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
-
-describe('shared.ops.buySpecialSpell', () => {
- let user;
- const analytics = { track () {} };
-
- async function buySpecialSpell (_user, _req, _analytics) {
- const buyOp = new BuySpellOperation(_user, _req, _analytics);
-
- return buyOp.purchase();
- }
- beforeEach(() => {
- user = generateUser();
- sinon.stub(analytics, 'track');
- });
-
- afterEach(() => {
- analytics.track.restore();
- });
-
- it('throws an error if params.key is missing', async () => {
- try {
- await buySpecialSpell(user);
- } catch (err) {
- expect(err).to.be.an.instanceof(BadRequest);
- expect(err.message).to.equal(errorMessage('missingKeyParam'));
- }
- });
-
- it('throws an error if the spell doesn\'t exists', async () => {
- try {
- await buySpecialSpell(user, {
- params: {
- key: 'notExisting',
- },
- });
- } catch (err) {
- expect(err).to.be.an.instanceof(NotFound);
- expect(err.message).to.equal(errorMessage('spellNotFound', { spellId: 'notExisting' }));
- }
- });
-
- it('throws an error if the user doesn\'t have enough gold', async () => {
- user.stats.gp = 1;
- try {
- await buySpecialSpell(user, {
- params: {
- key: 'thankyou',
- },
- });
- } catch (err) {
- expect(err).to.be.an.instanceof(NotAuthorized);
- expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
- }
- });
-
- it('buys an item', async () => {
- user.stats.gp = 11;
- const item = content.special.thankyou;
-
- const [data, message] = await buySpecialSpell(user, {
- params: {
- key: 'thankyou',
- },
- }, analytics);
-
- expect(user.stats.gp).to.equal(1);
- expect(user.items.special.thankyou).to.equal(1);
- expect(data).to.eql({
- items: user.items,
- stats: user.stats,
- });
- expect(message).to.equal(i18n.t('messageBought', {
- itemText: item.text(),
- }));
- expect(analytics.track).to.be.calledOnce;
- });
-});
diff --git a/test/common/ops/buy/buySpell.test.js b/test/common/ops/buy/buySpell.test.js
new file mode 100644
index 0000000000..f914ca8c6f
--- /dev/null
+++ b/test/common/ops/buy/buySpell.test.js
@@ -0,0 +1,172 @@
+import { BuySpellOperation } from '../../../../website/common/script/ops/buy/buySpell';
+import {
+ BadRequest,
+ NotFound,
+ NotAuthorized,
+} from '../../../../website/common/script/libs/errors';
+import i18n from '../../../../website/common/script/i18n';
+import {
+ generateUser,
+} from '../../../helpers/common.helper';
+import content from '../../../../website/common/script/content/index';
+import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
+
+describe('shared.ops.buySpecialSpell', () => {
+ let user;
+ let clock;
+ const analytics = { track () {} };
+
+ async function buySpecialSpell (_user, _req, _analytics) {
+ const buyOp = new BuySpellOperation(_user, _req, _analytics);
+
+ return buyOp.purchase();
+ }
+ beforeEach(() => {
+ user = generateUser();
+ sinon.stub(analytics, 'track');
+ });
+
+ afterEach(() => {
+ analytics.track.restore();
+ if (clock) {
+ clock.restore();
+ }
+ });
+
+ it('throws an error if params.key is missing', async () => {
+ try {
+ await buySpecialSpell(user);
+ } catch (err) {
+ expect(err).to.be.an.instanceof(BadRequest);
+ expect(err.message).to.equal(errorMessage('missingKeyParam'));
+ }
+ });
+
+ it('throws an error if the item doesn\'t exists', async () => {
+ try {
+ await buySpecialSpell(user, {
+ params: {
+ key: 'notExisting',
+ },
+ });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotFound);
+ expect(err.message).to.equal(errorMessage('spellNotFound', { spellId: 'notExisting' }));
+ }
+ });
+
+ it('throws an error if the user doesn\'t have enough gold', async () => {
+ user.stats.gp = 1;
+ try {
+ await buySpecialSpell(user, {
+ params: {
+ key: 'thankyou',
+ },
+ });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
+ }
+ });
+
+ describe('buying cards', () => {
+ it('buys a card that is always available', async () => {
+ user.stats.gp = 11;
+ const item = content.special.thankyou;
+
+ const [data, message] = await buySpecialSpell(user, {
+ params: {
+ key: 'thankyou',
+ },
+ }, analytics);
+
+ expect(user.stats.gp).to.equal(1);
+ expect(user.items.special.thankyou).to.equal(1);
+ expect(data).to.eql({
+ items: user.items,
+ stats: user.stats,
+ });
+ expect(message).to.equal(i18n.t('messageBought', {
+ itemText: item.text(),
+ }));
+ expect(analytics.track).to.be.calledOnce;
+ });
+
+ it('buys a limited card when it is available', async () => {
+ user.stats.gp = 11;
+ const item = content.special.nye;
+ clock = sinon.useFakeTimers(new Date('2024-01-01'));
+
+ const [data, message] = await buySpecialSpell(user, {
+ params: {
+ key: 'nye',
+ },
+ }, analytics);
+
+ expect(user.stats.gp).to.equal(1);
+ expect(user.items.special.nye).to.equal(1);
+ expect(data).to.eql({
+ items: user.items,
+ stats: user.stats,
+ });
+ expect(message).to.equal(i18n.t('messageBought', {
+ itemText: item.text(),
+ }));
+ expect(analytics.track).to.be.calledOnce;
+ });
+
+ it('throws an error if the card is not currently available', async () => {
+ user.stats.gp = 11;
+ clock = sinon.useFakeTimers(new Date('2024-06-01'));
+ try {
+ await buySpecialSpell(user, {
+ params: {
+ key: 'nye',
+ },
+ });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('cannotBuyItem'));
+ }
+ });
+ });
+
+ describe('buying spells', () => {
+ it('buys a spell if it is currently available', async () => {
+ user.stats.gp = 16;
+ clock = sinon.useFakeTimers(new Date('2024-06-22'));
+ const item = content.special.seafoam;
+ const [data, message] = await buySpecialSpell(user, {
+ params: {
+ key: 'seafoam',
+ },
+ }, analytics);
+
+ expect(user.stats.gp).to.equal(1);
+ expect(user.items.special.seafoam).to.equal(1);
+ expect(data).to.eql({
+ items: user.items,
+ stats: user.stats,
+ });
+ expect(message).to.equal(i18n.t('messageBought', {
+ itemText: item.text(),
+ }));
+ expect(analytics.track).to.be.calledOnce;
+ });
+
+ it('throws an error if the spell is not currently available', async () => {
+ user.stats.gp = 50;
+ clock = sinon.useFakeTimers(new Date('2024-01-22'));
+ try {
+ await buySpecialSpell(user, {
+ params: {
+ key: 'seafoam',
+ },
+ });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('cannotBuyItem'));
+ }
+ });
+ });
+});
diff --git a/test/common/ops/buy/hourglassPurchase.js b/test/common/ops/buy/hourglassPurchase.test.js
similarity index 100%
rename from test/common/ops/buy/hourglassPurchase.js
rename to test/common/ops/buy/hourglassPurchase.test.js
diff --git a/test/common/ops/buy/purchase.js b/test/common/ops/buy/purchase.test.js
similarity index 67%
rename from test/common/ops/buy/purchase.js
rename to test/common/ops/buy/purchase.test.js
index 8f70523d1c..a61b3a0d59 100644
--- a/test/common/ops/buy/purchase.js
+++ b/test/common/ops/buy/purchase.test.js
@@ -15,6 +15,7 @@ import {
describe('shared.ops.purchase', () => {
const SEASONAL_FOOD = moment().isBefore('2021-11-02T20:00-04:00') ? 'Candy_Base' : 'Meat';
let user;
+ let clock;
const goldPoints = 40;
const analytics = { track () {} };
@@ -25,11 +26,13 @@ describe('shared.ops.purchase', () => {
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
+ clock = sandbox.useFakeTimers(new Date('2024-01-10'));
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
+ clock.restore();
});
context('failure conditions', () => {
@@ -82,13 +85,77 @@ describe('shared.ops.purchase', () => {
it('returns error when user does not have enough gems to buy an item', async () => {
try {
- await purchase(user, { params: { type: 'gear', key: 'headAccessory_special_wolfEars' } });
+ await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2019Healer' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughGems'));
}
});
+ it('returns error when gear is not available', async () => {
+ try {
+ await purchase(user, { params: { type: 'gear', key: 'shield_special_spring2019Healer' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
+ it('returns an error when purchasing current seasonal gear', async () => {
+ user.balance = 2;
+ try {
+ await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2024Healer' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
+ it('returns error when hatching potion is not available', async () => {
+ try {
+ await purchase(user, { params: { type: 'hatchingPotions', key: 'PolkaDot' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
+ it('returns error when quest for hatching potion was not yet completed', async () => {
+ try {
+ await purchase(user, { params: { type: 'hatchingPotions', key: 'BlackPearl' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
+ it('returns error when quest for egg was not yet completed', async () => {
+ try {
+ await purchase(user, { params: { type: 'eggs', key: 'Octopus' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
+ it('returns error when bundle is not available', async () => {
+ try {
+ await purchase(user, { params: { type: 'bundles', key: 'forestFriends' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('notAvailable'));
+ }
+ });
+
+ it('returns error when gear is not gem purchasable', async () => {
+ try {
+ await purchase(user, { params: { type: 'gear', key: 'shield_healer_3' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(NotAuthorized);
+ expect(err.message).to.equal(i18n.t('messageNotAvailable'));
+ }
+ });
+
it('returns error when item is not found', async () => {
const params = { key: 'notExisting', type: 'food' };
@@ -99,44 +166,6 @@ describe('shared.ops.purchase', () => {
expect(err.message).to.equal(i18n.t('contentKeyNotFound', params));
}
});
-
- it('returns error when user supplies a non-numeric quantity', async () => {
- const type = 'eggs';
- const key = 'Wolf';
-
- try {
- await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
- } catch (err) {
- expect(err).to.be.an.instanceof(BadRequest);
- expect(err.message).to.equal(i18n.t('invalidQuantity'));
- }
- });
-
- it('returns error when user supplies a negative quantity', async () => {
- const type = 'eggs';
- const key = 'Wolf';
- user.balance = 10;
-
- try {
- await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
- } catch (err) {
- expect(err).to.be.an.instanceof(BadRequest);
- expect(err.message).to.equal(i18n.t('invalidQuantity'));
- }
- });
-
- it('returns error when user supplies a decimal quantity', async () => {
- const type = 'eggs';
- const key = 'Wolf';
- user.balance = 10;
-
- try {
- await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
- } catch (err) {
- expect(err).to.be.an.instanceof(BadRequest);
- expect(err.message).to.equal(i18n.t('invalidQuantity'));
- }
- });
});
context('successful purchase', () => {
@@ -150,7 +179,7 @@ describe('shared.ops.purchase', () => {
user.pinnedItems.push({ type: 'eggs', key: 'Wolf' });
user.pinnedItems.push({ type: 'hatchingPotions', key: 'Base' });
user.pinnedItems.push({ type: 'food', key: SEASONAL_FOOD });
- user.pinnedItems.push({ type: 'gear', key: 'headAccessory_special_tigerEars' });
+ user.pinnedItems.push({ type: 'gear', key: 'shield_special_winter2019Healer' });
user.pinnedItems.push({ type: 'bundles', key: 'featheredFriends' });
});
@@ -185,9 +214,9 @@ describe('shared.ops.purchase', () => {
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
- it('purchases gear', async () => {
+ it('purchases past seasonal gear', async () => {
const type = 'gear';
- const key = 'headAccessory_special_tigerEars';
+ const key = 'shield_special_winter2019Healer';
await purchase(user, { params: { type, key } });
@@ -195,9 +224,39 @@ describe('shared.ops.purchase', () => {
expect(pinnedGearUtils.removeItemByPath.calledOnce).to.equal(true);
});
+ it('purchases hatching potion', async () => {
+ const type = 'hatchingPotions';
+ const key = 'Peppermint';
+
+ await purchase(user, { params: { type, key } });
+
+ expect(user.items.hatchingPotions[key]).to.eql(1);
+ });
+
+ it('purchases hatching potion if user completed quest', async () => {
+ const type = 'hatchingPotions';
+ const key = 'Bronze';
+ user.achievements.quests.bronze = 1;
+
+ await purchase(user, { params: { type, key } });
+
+ expect(user.items.hatchingPotions[key]).to.eql(1);
+ });
+
+ it('purchases egg if user completed quest', async () => {
+ const type = 'eggs';
+ const key = 'Deer';
+ user.achievements.quests.ghost_stag = 1;
+
+ await purchase(user, { params: { type, key } });
+
+ expect(user.items.eggs[key]).to.eql(1);
+ });
+
it('purchases quest bundles', async () => {
const startingBalance = user.balance;
- const clock = sandbox.useFakeTimers(moment('2024-03-20').valueOf());
+ clock.restore();
+ clock = sandbox.useFakeTimers(moment('2022-03-10').valueOf());
const type = 'bundles';
const key = 'cuddleBuddies';
const price = 1.75;
@@ -216,7 +275,6 @@ describe('shared.ops.purchase', () => {
expect(user.balance).to.equal(startingBalance - price);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
- clock.restore();
});
});
@@ -257,5 +315,43 @@ describe('shared.ops.purchase', () => {
expect(user.items[type][key]).to.equal(2);
});
+
+ it('returns error when user supplies a non-numeric quantity', async () => {
+ const type = 'eggs';
+ const key = 'Wolf';
+
+ try {
+ await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
+ } catch (err) {
+ expect(err).to.be.an.instanceof(BadRequest);
+ expect(err.message).to.equal(i18n.t('invalidQuantity'));
+ }
+ });
+
+ it('returns error when user supplies a negative quantity', async () => {
+ const type = 'eggs';
+ const key = 'Wolf';
+ user.balance = 10;
+
+ try {
+ await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
+ } catch (err) {
+ expect(err).to.be.an.instanceof(BadRequest);
+ expect(err.message).to.equal(i18n.t('invalidQuantity'));
+ }
+ });
+
+ it('returns error when user supplies a decimal quantity', async () => {
+ const type = 'eggs';
+ const key = 'Wolf';
+ user.balance = 10;
+
+ try {
+ await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
+ } catch (err) {
+ expect(err).to.be.an.instanceof(BadRequest);
+ expect(err.message).to.equal(i18n.t('invalidQuantity'));
+ }
+ });
});
});
diff --git a/test/common/ops/unlock.js b/test/common/ops/unlock.test.js
similarity index 83%
rename from test/common/ops/unlock.js
rename to test/common/ops/unlock.test.js
index f08608e615..24aed655a5 100644
--- a/test/common/ops/unlock.js
+++ b/test/common/ops/unlock.test.js
@@ -2,14 +2,17 @@ import get from 'lodash/get';
import unlock from '../../../website/common/script/ops/unlock';
import i18n from '../../../website/common/script/i18n';
import { generateUser } from '../../helpers/common.helper';
-import { NotAuthorized, BadRequest } from '../../../website/common/script/libs/errors';
+import {
+ NotAuthorized,
+ BadRequest,
+} from '../../../website/common/script/libs/errors';
describe('shared.ops.unlock', () => {
let user;
- const unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
+ let clock;
+ const unlockPath = 'shirt.convict,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars';
const backgroundUnlockPath = 'background.giant_florals';
- const backgroundSetUnlockPath = 'background.archery_range,background.giant_florals,background.rainbows_end';
const hairUnlockPath = 'hair.color.rainbow,hair.color.yellow,hair.color.green,hair.color.purple,hair.color.blue,hair.color.TRUred';
const facialHairUnlockPath = 'hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3';
const usersStartingGems = 50 / 4;
@@ -17,6 +20,11 @@ describe('shared.ops.unlock', () => {
beforeEach(() => {
user = generateUser();
user.balance = usersStartingGems;
+ clock = sandbox.useFakeTimers(new Date('2024-04-10'));
+ });
+
+ afterEach(() => {
+ clock.restore();
});
it('returns an error when path is not provided', async () => {
@@ -31,7 +39,9 @@ describe('shared.ops.unlock', () => {
it('does not unlock lost gear', async () => {
user.items.gear.owned.headAccessory_special_bearEars = false;
- await unlock(user, { query: { path: 'items.gear.owned.headAccessory_special_bearEars' } });
+ await unlock(user, {
+ query: { path: 'items.gear.owned.headAccessory_special_bearEars' },
+ });
expect(user.balance).to.equal(usersStartingGems);
});
@@ -95,7 +105,9 @@ describe('shared.ops.unlock', () => {
it('returns an error if gear is not from the animal set', async () => {
try {
- await unlock(user, { query: { path: 'items.gear.owned.back_mystery_202004' } });
+ await unlock(user, {
+ query: { path: 'items.gear.owned.back_mystery_202004' },
+ });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
@@ -153,7 +165,6 @@ describe('shared.ops.unlock', () => {
await unlock(user, { query: { path: partialUnlockPaths[4] } });
await unlock(user, { query: { path: partialUnlockPaths[5] } });
await unlock(user, { query: { path: partialUnlockPaths[6] } });
- await unlock(user, { query: { path: partialUnlockPaths[7] } });
await unlock(user, { query: { path: unlockPath } });
});
@@ -163,7 +174,9 @@ describe('shared.ops.unlock', () => {
await unlock(user, { query: { path: backgroundUnlockPath } });
const afterBalance = user.balance;
- const response = await unlock(user, { query: { path: backgroundUnlockPath } });
+ const response = await unlock(user, {
+ query: { path: backgroundUnlockPath },
+ });
expect(user.balance).to.equal(afterBalance); // do not bill twice
expect(response.message).to.not.exist;
@@ -176,7 +189,9 @@ describe('shared.ops.unlock', () => {
await unlock(user, { query: { path: backgroundUnlockPath } }); // unlock
const afterBalance = user.balance;
await unlock(user, { query: { path: backgroundUnlockPath } }); // equip
- const response = await unlock(user, { query: { path: backgroundUnlockPath } });
+ const response = await unlock(user, {
+ query: { path: backgroundUnlockPath },
+ });
expect(user.balance).to.equal(afterBalance); // do not bill twice
expect(response.message).to.not.exist;
@@ -192,8 +207,9 @@ describe('shared.ops.unlock', () => {
individualPaths.forEach(path => {
expect(get(user.purchased, path)).to.be.true;
});
- expect(Object.keys(user.purchased.shirt).length)
- .to.equal(initialShirts + individualPaths.length);
+ expect(Object.keys(user.purchased.shirt).length).to.equal(
+ initialShirts + individualPaths.length,
+ );
expect(user.balance).to.equal(usersStartingGems - 1.25);
});
@@ -208,8 +224,9 @@ describe('shared.ops.unlock', () => {
individualPaths.forEach(path => {
expect(get(user.purchased, path)).to.be.true;
});
- expect(Object.keys(user.purchased.hair.color).length)
- .to.equal(initialHairColors + individualPaths.length);
+ expect(Object.keys(user.purchased.hair.color).length).to.equal(
+ initialHairColors + individualPaths.length,
+ );
expect(user.balance).to.equal(usersStartingGems - 1.25);
});
@@ -219,21 +236,28 @@ describe('shared.ops.unlock', () => {
const initialMustache = Object.keys(user.purchased.hair.mustache).length;
const initialBeard = Object.keys(user.purchased.hair.mustache).length;
- const [, message] = await unlock(user, { query: { path: facialHairUnlockPath } });
+ const [, message] = await unlock(user, {
+ query: { path: facialHairUnlockPath },
+ });
expect(message).to.equal(i18n.t('unlocked'));
const individualPaths = facialHairUnlockPath.split(',');
individualPaths.forEach(path => {
expect(get(user.purchased, path)).to.be.true;
});
- expect(Object.keys(user.purchased.hair.mustache).length + Object.keys(user.purchased.hair.beard).length) // eslint-disable-line max-len
+ expect(
+ Object.keys(user.purchased.hair.mustache).length
+ + Object.keys(user.purchased.hair.beard).length,
+ ) // eslint-disable-line max-len
.to.equal(initialMustache + initialBeard + individualPaths.length);
expect(user.balance).to.equal(usersStartingGems - 1.25);
});
it('unlocks a full set of gear', async () => {
const initialGear = Object.keys(user.items.gear.owned).length;
- const [, message] = await unlock(user, { query: { path: unlockGearSetPath } });
+ const [, message] = await unlock(user, {
+ query: { path: unlockGearSetPath },
+ });
expect(message).to.equal(i18n.t('unlocked'));
@@ -241,32 +265,21 @@ describe('shared.ops.unlock', () => {
individualPaths.forEach(path => {
expect(get(user, path)).to.be.true;
});
- expect(Object.keys(user.items.gear.owned).length)
- .to.equal(initialGear + individualPaths.length);
+ expect(Object.keys(user.items.gear.owned).length).to.equal(
+ initialGear + individualPaths.length,
+ );
expect(user.balance).to.equal(usersStartingGems - 1.25);
});
- it('unlocks a full set of backgrounds', async () => {
- const initialBackgrounds = Object.keys(user.purchased.background).length;
- const [, message] = await unlock(user, { query: { path: backgroundSetUnlockPath } });
-
- expect(message).to.equal(i18n.t('unlocked'));
- const individualPaths = backgroundSetUnlockPath.split(',');
- individualPaths.forEach(path => {
- expect(get(user.purchased, path)).to.be.true;
- });
- expect(Object.keys(user.purchased.background).length)
- .to.equal(initialBackgrounds + individualPaths.length);
- expect(user.balance).to.equal(usersStartingGems - 3.75);
- });
-
it('unlocks an item (appearance)', async () => {
const path = unlockPath.split(',')[0];
const initialShirts = Object.keys(user.purchased.shirt).length;
const [, message] = await unlock(user, { query: { path } });
expect(message).to.equal(i18n.t('unlocked'));
- expect(Object.keys(user.purchased.shirt).length).to.equal(initialShirts + 1);
+ expect(Object.keys(user.purchased.shirt).length).to.equal(
+ initialShirts + 1,
+ );
expect(get(user.purchased, path)).to.be.true;
expect(user.balance).to.equal(usersStartingGems - 0.5);
});
@@ -279,7 +292,9 @@ describe('shared.ops.unlock', () => {
const [, message] = await unlock(user, { query: { path } });
expect(message).to.equal(i18n.t('unlocked'));
- expect(Object.keys(user.purchased.hair.color).length).to.equal(initialColorHair + 1);
+ expect(Object.keys(user.purchased.hair.color).length).to.equal(
+ initialColorHair + 1,
+ );
expect(get(user.purchased, path)).to.be.true;
expect(user.balance).to.equal(usersStartingGems - 0.5);
});
@@ -295,8 +310,12 @@ describe('shared.ops.unlock', () => {
expect(message).to.equal(i18n.t('unlocked'));
- expect(Object.keys(user.purchased.hair.mustache).length).to.equal(initialMustache + 1);
- expect(Object.keys(user.purchased.hair.beard).length).to.equal(initialBeard);
+ expect(Object.keys(user.purchased.hair.mustache).length).to.equal(
+ initialMustache + 1,
+ );
+ expect(Object.keys(user.purchased.hair.beard).length).to.equal(
+ initialBeard,
+ );
expect(get(user.purchased, path)).to.be.true;
expect(user.balance).to.equal(usersStartingGems - 0.5);
@@ -315,11 +334,24 @@ describe('shared.ops.unlock', () => {
it('unlocks an item (background)', async () => {
const initialBackgrounds = Object.keys(user.purchased.background).length;
- const [, message] = await unlock(user, { query: { path: backgroundUnlockPath } });
+ const [, message] = await unlock(user, {
+ query: { path: backgroundUnlockPath },
+ });
expect(message).to.equal(i18n.t('unlocked'));
- expect(Object.keys(user.purchased.background).length).to.equal(initialBackgrounds + 1);
+ expect(Object.keys(user.purchased.background).length).to.equal(
+ initialBackgrounds + 1,
+ );
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
expect(user.balance).to.equal(usersStartingGems - 1.75);
});
+
+ it('handles an invalid hair path gracefully', async () => {
+ try {
+ await unlock(user, { query: { path: 'hair.invalid' } });
+ } catch (err) {
+ expect(err).to.be.an.instanceof(BadRequest);
+ expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
+ }
+ });
});
diff --git a/test/common/test_helper.js b/test/common/test_helper.js
deleted file mode 100644
index c04e76f478..0000000000
--- a/test/common/test_helper.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* eslint-disable prefer-template, no-shadow, func-names, import/no-commonjs */
-
-const expect = require('expect.js');
-
-module.exports.addCustomMatchers = function () {
- const { Assertion } = expect;
- Assertion.prototype.toHaveGP = function (gp) {
- const actual = this.obj.stats.gp;
- return this.assert(actual === gp, () => 'expected user to have ' + gp + ' gp, but got ' + actual, () => 'expected user to not have ' + gp + ' gp');
- };
- Assertion.prototype.toHaveHP = function (hp) {
- const actual = this.obj.stats.hp;
- return this.assert(actual === hp, () => 'expected user to have ' + hp + ' hp, but got ' + actual, () => 'expected user to not have ' + hp + ' hp');
- };
- Assertion.prototype.toHaveExp = function (exp) {
- const actual = this.obj.stats.exp;
- return this.assert(actual === exp, () => 'expected user to have ' + exp + ' experience points, but got ' + actual, () => 'expected user to not have ' + exp + ' experience points');
- };
- Assertion.prototype.toHaveLevel = function (lvl) {
- const actual = this.obj.stats.lvl;
- return this.assert(actual === lvl, () => 'expected user to be level ' + lvl + ', but got ' + actual, () => 'expected user to not be level ' + lvl);
- };
- Assertion.prototype.toHaveMaxMP = function (mp) {
- const actual = this.obj._statsComputed.maxMP;
- return this.assert(actual === mp, () => 'expected user to have ' + mp + ' max mp, but got ' + actual, () => 'expected user to not have ' + mp + ' max mp');
- };
-};
diff --git a/test/content/armoire.test.js b/test/content/armoire.test.js
new file mode 100644
index 0000000000..cbcb0e253c
--- /dev/null
+++ b/test/content/armoire.test.js
@@ -0,0 +1,83 @@
+/* eslint-disable global-require */
+import forEach from 'lodash/forEach';
+import {
+ expectValidTranslationString,
+} from '../helpers/content.helper';
+
+function makeArmoireIitemList () {
+ const armoire = require('../../website/common/script/content/gear/sets/armoire').default;
+ const items = [];
+ items.push(...Object.values(armoire.armor));
+ items.push(...Object.values(armoire.body));
+ items.push(...Object.values(armoire.eyewear));
+ items.push(...Object.values(armoire.head));
+ items.push(...Object.values(armoire.headAccessory));
+ items.push(...Object.values(armoire.shield));
+ items.push(...Object.values(armoire.weapon));
+ return items;
+}
+
+describe('armoire', () => {
+ let clock;
+ beforeEach(() => {
+ delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
+ });
+ afterEach(() => {
+ clock.restore();
+ });
+ it('does not return unreleased gear', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-02'));
+ const items = makeArmoireIitemList();
+ expect(items.length).to.equal(377);
+ expect(items.filter(item => item.set === 'pottersSet' || item.set === 'optimistSet' || item.set === 'schoolUniform')).to.be.an('array').that.is.empty;
+ });
+
+ it('released gear has all required properties', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-05-08'));
+ const items = makeArmoireIitemList();
+ expect(items.length).to.equal(396);
+ forEach(items, item => {
+ if (item.set !== undefined) {
+ expect(item.set, item.key).to.be.a('string');
+ expect(item.set, item.key).to.not.be.empty;
+ }
+ expectValidTranslationString(item.text);
+ expect(item.value, item.key).to.be.a('number');
+ });
+ });
+
+ it('releases gear when appropriate', async () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-01T00:00:00.000Z'));
+ const items = makeArmoireIitemList();
+ expect(items.length).to.equal(377);
+ clock.restore();
+ delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
+ clock = sinon.useFakeTimers(new Date('2024-01-08'));
+ const januaryItems = makeArmoireIitemList();
+ expect(januaryItems.length).to.equal(381);
+ clock.restore();
+ delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
+ clock = sinon.useFakeTimers(new Date('2024-02-07'));
+ const januaryItems2 = makeArmoireIitemList();
+ expect(januaryItems2.length).to.equal(381);
+ clock.restore();
+ delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
+ clock = sinon.useFakeTimers(new Date('2024-02-07T16:00:00.000Z'));
+ const febuaryItems = makeArmoireIitemList();
+ expect(febuaryItems.length).to.equal(384);
+ });
+
+ it('sets have at least 2 items', () => {
+ const armoire = makeArmoireIitemList();
+ const setMap = {};
+ forEach(armoire, item => {
+ if (setMap[item.set] === undefined) {
+ setMap[item.set] = 0;
+ }
+ setMap[item.set] += 1;
+ });
+ Object.keys(setMap).forEach(set => {
+ expect(setMap[set], set).to.be.at.least(2);
+ });
+ });
+});
diff --git a/test/content/events.test.js b/test/content/events.test.js
new file mode 100644
index 0000000000..03ffd08ed9
--- /dev/null
+++ b/test/content/events.test.js
@@ -0,0 +1,40 @@
+import { getRepeatingEvents } from '../../website/common/script/content/constants/events';
+
+describe('events', () => {
+ let clock;
+
+ afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ });
+
+ it('returns empty array when no events are active', () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-06'));
+ const events = getRepeatingEvents();
+ expect(events).to.be.empty;
+ });
+
+ it('returns events when active', () => {
+ clock = sinon.useFakeTimers(new Date('2024-01-31'));
+ const events = getRepeatingEvents();
+ expect(events).to.have.length(1);
+ expect(events[0].key).to.equal('birthday');
+ expect(events[0].end).to.be.greaterThan(new Date());
+ expect(events[0].start).to.be.lessThan(new Date());
+ });
+
+ it('returns nye event at beginning of the year', () => {
+ clock = sinon.useFakeTimers(new Date('2025-01-01'));
+ const events = getRepeatingEvents();
+ expect(events).to.have.length(1);
+ expect(events[0].key).to.equal('nye');
+ });
+
+ it('returns nye event at end of the year', () => {
+ clock = sinon.useFakeTimers(new Date('2024-12-30'));
+ const events = getRepeatingEvents();
+ expect(events).to.have.length(1);
+ expect(events[0].key).to.equal('nye');
+ });
+});
diff --git a/test/content/food.test.js b/test/content/food.test.js
new file mode 100644
index 0000000000..5cdb2b80bc
--- /dev/null
+++ b/test/content/food.test.js
@@ -0,0 +1,94 @@
+/* eslint-disable global-require */
+import {
+ each,
+} from 'lodash';
+import {
+ expectValidTranslationString,
+} from '../helpers/content.helper';
+import content from '../../website/common/script/content';
+
+describe('food', () => {
+ let clock;
+
+ afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ delete require.cache[require.resolve('../../website/common/script/content')];
+ });
+
+ describe('all', () => {
+ it('contains basic information about each food item', () => {
+ each(content.food, (foodItem, key) => {
+ if (foodItem.key === 'Saddle') {
+ expectValidTranslationString(foodItem.sellWarningNote);
+ } else {
+ expectValidTranslationString(foodItem.textA);
+ expectValidTranslationString(foodItem.textThe);
+ expect(foodItem.target).to.be.a('string');
+ }
+ expectValidTranslationString(foodItem.text);
+ expectValidTranslationString(foodItem.notes);
+ expect(foodItem.canBuy).to.be.a('function');
+ expect(foodItem.value).to.be.a('number');
+ expect(foodItem.key).to.equal(key);
+ });
+ });
+
+ it('sets canDrop for normal food if there is no food season', () => {
+ clock = sinon.useFakeTimers(new Date(2024, 5, 8));
+ const datedContent = require('../../website/common/script/content').default;
+ each(datedContent.food, foodItem => {
+ if (foodItem.key.indexOf('Cake') === -1 && foodItem.key.indexOf('Candy_') === -1 && foodItem.key.indexOf('Pie_') === -1 && foodItem.key !== 'Saddle') {
+ expect(foodItem.canDrop).to.equal(true);
+ } else {
+ expect(foodItem.canDrop).to.equal(false);
+ }
+ });
+ });
+
+ it('sets canDrop for candy if it is candy season', () => {
+ clock = sinon.useFakeTimers(new Date(2024, 9, 31));
+ const datedContent = require('../../website/common/script/content').default;
+ each(datedContent.food, foodItem => {
+ if (foodItem.key.indexOf('Candy_') !== -1) {
+ expect(foodItem.canDrop).to.equal(true);
+ } else {
+ expect(foodItem.canDrop).to.equal(false);
+ }
+ });
+ });
+
+ it('sets canDrop for cake if it is cake season', () => {
+ clock = sinon.useFakeTimers(new Date(2024, 0, 31));
+ const datedContent = require('../../website/common/script/content').default;
+ each(datedContent.food, foodItem => {
+ if (foodItem.key.indexOf('Cake_') !== -1) {
+ expect(foodItem.canDrop).to.equal(true);
+ } else {
+ expect(foodItem.canDrop).to.equal(false);
+ }
+ });
+ });
+
+ it('sets canDrop for pie if it is pie season', () => {
+ clock = sinon.useFakeTimers(new Date(2024, 2, 14));
+ const datedContent = require('../../website/common/script/content').default;
+ each(datedContent.food, foodItem => {
+ if (foodItem.key.indexOf('Pie_') !== -1) {
+ expect(foodItem.canDrop).to.equal(true);
+ } else {
+ expect(foodItem.canDrop).to.equal(false);
+ }
+ });
+ });
+ });
+
+ it('sets correct values for saddles', () => {
+ const saddle = content.food.Saddle;
+ expect(saddle.canBuy).to.be.a('function');
+ expect(saddle.value).to.equal(5);
+ expect(saddle.key).to.equal('Saddle');
+ expect(saddle.canDrop).to.equal(false);
+ });
+});
diff --git a/test/content/gear.js b/test/content/gear.test.js
similarity index 77%
rename from test/content/gear.js
rename to test/content/gear.test.js
index 49c6fe0e87..3268ef6b2d 100644
--- a/test/content/gear.js
+++ b/test/content/gear.test.js
@@ -4,6 +4,8 @@ import {
expectValidTranslationString,
} from '../helpers/content.helper';
+import { CLASSES } from '../../website/common/script/content/constants';
+
import gearData from '../../website/common/script/content/gear';
import * as backerGear from '../../website/common/script/content/gear/sets/special/special-backer';
import * as contributorGear from '../../website/common/script/content/gear/sets/special/special-contributor';
@@ -17,35 +19,48 @@ describe('Gear', () => {
context(`${klass} ${gearType}s`, () => {
it('have a value of at least 0 for each stat', () => {
each(items, gear => {
- expect(gear.con).to.be.at.least(0);
- expect(gear.int).to.be.at.least(0);
- expect(gear.per).to.be.at.least(0);
- expect(gear.str).to.be.at.least(0);
+ expect(gear.con, gear.key).to.be.at.least(0);
+ expect(gear.int, gear.key).to.be.at.least(0);
+ expect(gear.per, gear.key).to.be.at.least(0);
+ expect(gear.str, gear.key).to.be.at.least(0);
});
});
it('have a purchase value of at least 0', () => {
each(items, gear => {
- expect(gear.value).to.be.at.least(0);
+ expect(gear.value, gear.key).to.be.at.least(0);
});
});
it('has a canBuy function', () => {
each(items, gear => {
- expect(gear.canBuy).to.be.a('function');
+ expect(gear.canBuy, gear.key).to.be.a('function');
});
});
it('have valid translation strings for text and notes', () => {
each(items, gear => {
- expectValidTranslationString(gear.text);
- expectValidTranslationString(gear.notes);
+ expectValidTranslationString(gear.text, gear.key);
+ expectValidTranslationString(gear.notes, gear.key);
});
});
it('has a set attribue', () => {
each(items, gear => {
- expect(gear.set).to.exist;
+ expect(gear.set, gear.key).to.exist;
+ });
+ });
+
+ it('has a valid value for klass or specialClass', () => {
+ const validClassValues = CLASSES + ['base', 'mystery', 'armoire'];
+ each(items, gear => {
+ const field = gear.klass === 'special' ? gear.specialClass : gear.klass;
+ if (gear.klass === 'special' && field === undefined) {
+ // some special gear doesn't have a klass
+ return;
+ }
+ expect(field, gear.key).to.exist;
+ expect(validClassValues, gear.key).to.include(field);
});
});
});
@@ -53,6 +68,16 @@ describe('Gear', () => {
});
});
+ it('only assigns mage weapons twoHanded', () => {
+ each([allGear.armor.special, allGear.head.special, allGear.shield.special], gearType => {
+ each(gearType, gear => {
+ if (gear.specialClass === 'wizard') {
+ expect(gear.twoHanded, gear.key).to.not.eql(true);
+ }
+ });
+ });
+ });
+
describe('backer gear', () => {
let user;
diff --git a/test/content/hatching-potions.test.js b/test/content/hatching-potions.test.js
index a4e31d7b91..1d1fd575a5 100644
--- a/test/content/hatching-potions.test.js
+++ b/test/content/hatching-potions.test.js
@@ -5,27 +5,33 @@ import {
expectValidTranslationString,
} from '../helpers/content.helper';
-import * as hatchingPotions from '../../website/common/script/content/hatching-potions';
+import { all } from '../../website/common/script/content/hatching-potions';
describe('hatchingPotions', () => {
- describe('all', () => {
- it('is a combination of drop, premium, and wacky potions', () => {
- const dropNumber = Object.keys(hatchingPotions.drops).length;
- const premiumNumber = Object.keys(hatchingPotions.premium).length;
- const wackyNumber = Object.keys(hatchingPotions.wacky).length;
- const allNumber = Object.keys(hatchingPotions.all).length;
+ let clock;
- expect(allNumber).to.be.greaterThan(0);
- expect(allNumber).to.equal(dropNumber + premiumNumber + wackyNumber);
- });
+ afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ });
- it('contains basic information about each potion', () => {
- each(hatchingPotions.all, (potion, key) => {
- expectValidTranslationString(potion.text);
- expectValidTranslationString(potion.notes);
- expect(potion.canBuy).to.be.a('function');
- expect(potion.value).to.be.a('number');
- expect(potion.key).to.equal(key);
+ const potionTypes = [
+ 'drops',
+ 'quests',
+ 'premium',
+ 'wacky',
+ ];
+ potionTypes.forEach(potionType => {
+ describe(potionType, () => {
+ it('contains basic information about each potion', () => {
+ each(all, (potion, key) => {
+ expectValidTranslationString(potion.text);
+ expectValidTranslationString(potion.notes);
+ expect(potion.canBuy).to.be.a('function');
+ expect(potion.value).to.be.a('number');
+ expect(potion.key).to.equal(key);
+ });
});
});
});
diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js
new file mode 100644
index 0000000000..09d4ebfd6f
--- /dev/null
+++ b/test/content/schedule.test.js
@@ -0,0 +1,271 @@
+// eslint-disable-next-line max-len
+import moment from 'moment';
+import nconf from 'nconf';
+import {
+ getAllScheduleMatchingGroups, clearCachedMatchers, MONTHLY_SCHEDULE, GALA_SCHEDULE,
+} from '../../website/common/script/content/constants/schedule';
+import QUEST_PETS from '../../website/common/script/content/quests/pets';
+import QUEST_HATCHINGPOTIONS from '../../website/common/script/content/quests/potions';
+import QUEST_BUNDLES from '../../website/common/script/content/bundles';
+import { premium } from '../../website/common/script/content/hatching-potions';
+import SPELLS from '../../website/common/script/content/spells';
+import QUEST_SEASONAL from '../../website/common/script/content/quests/seasonal';
+
+function validateMatcher (matcher, checkedDate) {
+ expect(matcher.end).to.be.a('date');
+ expect(matcher.end).to.be.greaterThan(checkedDate);
+}
+
+describe('Content Schedule', () => {
+ let switchoverTime;
+
+ beforeEach(() => {
+ switchoverTime = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
+ clearCachedMatchers();
+ });
+
+ it('assembles scheduled items on january 15th', () => {
+ const date = new Date('2024-01-15');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('assembles scheduled items on january 31th', () => {
+ const date = new Date('2024-01-31');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('assembles scheduled items on march 2nd', () => {
+ const date = new Date('2024-03-02');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('assembles scheduled items on march 22st', () => {
+ const date = new Date('2024-03-22');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('assembles scheduled items on october 7th', () => {
+ const date = new Date('2024-10-07');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+ it('assembles scheduled items on november 1th', () => {
+ const date = new Date('2024-11-01');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('assembles scheduled items on december 20th', () => {
+ const date = new Date('2024-12-20');
+ const matchers = getAllScheduleMatchingGroups(date);
+ for (const key in matchers) {
+ if (matchers[key]) {
+ validateMatcher(matchers[key], date);
+ }
+ }
+ });
+
+ it('sets the end date if its in the same month', () => {
+ const date = new Date('2024-04-03');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-04-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
+ });
+
+ it('sets the end date if its in the next day', () => {
+ const date = new Date('2024-05-06T14:00:00.000Z');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-05-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
+ });
+
+ it('sets the end date if its on the release day', () => {
+ const date = new Date('2024-05-07T07:00:00.000Z');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-06-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
+ });
+
+ it('sets the end date if its next month', () => {
+ const date = new Date('2024-05-20T01:00:00.000Z');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.backgrounds.end).to.eql(moment.utc(`2024-06-07T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
+ });
+
+ it('sets the end date for a gala', () => {
+ const date = new Date('2024-05-20');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
+ });
+
+ it('contains content for repeating events', () => {
+ const date = new Date('2024-04-15');
+ const matchers = getAllScheduleMatchingGroups(date);
+ expect(matchers.premiumHatchingPotions).to.exist;
+ expect(matchers.premiumHatchingPotions.items.length).to.equal(4);
+ expect(matchers.premiumHatchingPotions.items.indexOf('Garden')).to.not.equal(-1);
+ expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
+ });
+
+ describe('only contains valid keys for', () => {
+ it('pet quests', () => {
+ const petKeys = Object.keys(QUEST_PETS);
+ Object.keys(MONTHLY_SCHEDULE).forEach(key => {
+ const petQuests = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'petQuests');
+ for (const petQuest of petQuests.items) {
+ expect(petQuest).to.be.a('string');
+ expect(petKeys).to.include(petQuest);
+ }
+ });
+ });
+
+ it('hatchingpotion quests', () => {
+ const potionKeys = Object.keys(QUEST_HATCHINGPOTIONS);
+ Object.keys(MONTHLY_SCHEDULE).forEach(key => {
+ const potionQuests = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'hatchingPotionQuests');
+ for (const potionQuest of potionQuests.items) {
+ expect(potionQuest).to.be.a('string');
+ expect(potionKeys).to.include(potionQuest);
+ }
+ });
+ });
+
+ it('bundles', () => {
+ const bundleKeys = Object.keys(QUEST_BUNDLES);
+ Object.keys(MONTHLY_SCHEDULE).forEach(key => {
+ const bundles = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'bundles');
+ for (const bundle of bundles.items) {
+ expect(bundle).to.be.a('string');
+ expect(bundleKeys).to.include(bundle);
+ }
+ });
+ });
+
+ it('premium hatching potions', () => {
+ const potionKeys = Object.keys(premium);
+ Object.keys(MONTHLY_SCHEDULE).forEach(key => {
+ const monthlyPotions = MONTHLY_SCHEDULE[key][21].find(item => item.type === 'premiumHatchingPotions');
+ for (const potion of monthlyPotions.items) {
+ expect(potion).to.be.a('string');
+ expect(potionKeys).to.include(potion);
+ }
+ });
+ });
+
+ it('seasonal quests', () => {
+ const questKeys = Object.keys(QUEST_SEASONAL);
+ Object.keys(GALA_SCHEDULE).forEach(key => {
+ const quests = GALA_SCHEDULE[key].matchers.find(item => item.type === 'seasonalQuests');
+ for (const quest of quests.items) {
+ expect(quest).to.be.a('string');
+ expect(questKeys).to.include(quest);
+ }
+ });
+ });
+
+ it('seasonal spells', () => {
+ const spellKeys = Object.keys(SPELLS.special);
+ Object.keys(GALA_SCHEDULE).forEach(key => {
+ const petQuests = GALA_SCHEDULE[key].matchers.find(item => item.type === 'seasonalSpells');
+ for (const petQuest of petQuests.items) {
+ expect(petQuest).to.be.a('string');
+ expect(spellKeys).to.include(petQuest);
+ }
+ });
+ });
+ });
+
+ describe('backgrounds matcher', () => {
+ it('allows background matching the month for new backgrounds', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey072024')).to.be.true;
+ });
+
+ it('disallows background in the future', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey072025')).to.be.false;
+ });
+
+ it('disallows background for the inverse month for new backgrounds', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey012024')).to.be.false;
+ });
+
+ it('allows background for the inverse month for old backgrounds', () => {
+ const date = new Date('2024-08-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey022023')).to.be.true;
+ expect(matcher.match('backgroundkey022021')).to.be.true;
+ });
+
+ it('allows background even yeared backgrounds in first half of year', () => {
+ const date = new Date('2025-02-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey022024')).to.be.true;
+ expect(matcher.match('backgroundkey082022')).to.be.true;
+ });
+
+ it('allows background odd yeared backgrounds in second half of year', () => {
+ const date = new Date('2024-08-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('backgroundkey022023')).to.be.true;
+ expect(matcher.match('backgroundkey082021')).to.be.true;
+ });
+ });
+
+ describe('timeTravelers matcher', () => {
+ it('allows sets matching the month', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
+ expect(matcher.match('202307')).to.be.true;
+ expect(matcher.match('202207')).to.be.true;
+ });
+
+ it('disallows sets not matching the month', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
+ expect(matcher.match('202306')).to.be.false;
+ expect(matcher.match('202402')).to.be.false;
+ });
+
+ it('disallows sets from current month', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
+ expect(matcher.match('202407')).to.be.false;
+ });
+
+ it('disallows sets from the future', () => {
+ const date = new Date('2024-07-08');
+ const matcher = getAllScheduleMatchingGroups(date).backgrounds;
+ expect(matcher.match('202507')).to.be.false;
+ });
+ });
+});
diff --git a/test/content/shop-featuredItems.test.js b/test/content/shop-featuredItems.test.js
new file mode 100644
index 0000000000..6ec7d57592
--- /dev/null
+++ b/test/content/shop-featuredItems.test.js
@@ -0,0 +1,52 @@
+import Sinon from 'sinon';
+import featuredItems from '../../website/common/script/content/shop-featuredItems';
+
+describe('Shop Featured Items', () => {
+ let clock;
+
+ afterEach(() => {
+ if (clock !== undefined) {
+ clock.restore();
+ clock = undefined;
+ }
+ });
+
+ describe('Market', () => {
+ it('contains armoire', () => {
+ const items = featuredItems.market();
+ expect(_.find(items, item => item.path === 'armoire')).to.exist;
+ });
+
+ it('contains the current premium hatching potions', () => {
+ clock = Sinon.useFakeTimers(new Date('2024-04-08'));
+ const items = featuredItems.market();
+ expect(_.find(items, item => item.path === 'premiumHatchingPotions.Porcelain')).to.exist;
+ });
+
+ it('is featuring 4 items', () => {
+ clock = Sinon.useFakeTimers(new Date('2024-02-08'));
+ const items = featuredItems.market();
+ expect(items.length).to.eql(4);
+ });
+ });
+
+ describe('Quest Shop', () => {
+ it('contains bundle', () => {
+ clock = Sinon.useFakeTimers(new Date('2024-03-08'));
+ const items = featuredItems.quests();
+ expect(_.find(items, item => item.path === 'quests.pinkMarble')).to.exist;
+ });
+
+ it('contains pet quests', () => {
+ clock = Sinon.useFakeTimers(new Date('2024-04-08'));
+ const items = featuredItems.quests();
+ expect(_.find(items, item => item.path === 'quests.frog')).to.exist;
+ });
+
+ it('is featuring 3 items', () => {
+ clock = Sinon.useFakeTimers(new Date('2024-02-08'));
+ const items = featuredItems.quests();
+ expect(items.length).to.eql(3);
+ });
+ });
+});
diff --git a/test/content/spells.test.js b/test/content/spells.test.js
new file mode 100644
index 0000000000..23f0cecb93
--- /dev/null
+++ b/test/content/spells.test.js
@@ -0,0 +1,63 @@
+import {
+ generateUser,
+} from '../helpers/common.helper';
+import spells from '../../website/common/script/content/spells';
+import {
+ expectValidTranslationString,
+} from '../helpers/content.helper';
+import { TRANSFORMATION_DEBUFFS_LIST } from '../../website/common/script/constants';
+
+// TODO complete the test suite...
+
+describe('shared.ops.spells', () => {
+ let user;
+ let target;
+
+ beforeEach(() => {
+ user = generateUser();
+ target = generateUser();
+ });
+
+ it('all spells have required properties', () => {
+ for (const category of Object.values(spells)) {
+ for (const spell of Object.values(category)) {
+ expectValidTranslationString(spell.text, spell.key);
+ expectValidTranslationString(spell.notes);
+ expect(spell.target, spell.key).to.be.oneOf(['self', 'party', 'task', 'tasks', 'user']);
+ }
+ }
+ });
+
+ it('all special spells have a working cast method', async () => {
+ for (const s of Object.values(spells.special)) {
+ user.items.special[s.key] = 1;
+ s.cast(user, target, { language: 'en' });
+ }
+ });
+
+ it('all debuff spells cost 5 gold', () => {
+ for (const s of Object.values(spells.special)) {
+ if (s.purchaseType === 'debuffPotion') {
+ user.stats.gp = 5;
+ s.cast(user);
+ expect(user.stats.gp).to.equal(0);
+ }
+ }
+ });
+
+ it('all debuff spells remove the buff', () => {
+ const debuffMapping = {};
+ Object.keys(TRANSFORMATION_DEBUFFS_LIST).forEach(key => {
+ debuffMapping[TRANSFORMATION_DEBUFFS_LIST[key]] = key;
+ });
+ for (const s of Object.values(spells.special)) {
+ if (s.purchaseType === 'debuffPotion') {
+ user.stats.gp = 5;
+ user.stats.buffs[debuffMapping[s.key]] = true;
+ expect(user.stats.buffs[debuffMapping[s.key]]).to.equal(true);
+ s.cast(user);
+ expect(user.stats.buffs[debuffMapping[s.key]]).to.equal(false);
+ }
+ }
+ });
+});
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/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js
index f56f2c477a..3cbc2a53e3 100644
--- a/test/helpers/api-unit.helper.js
+++ b/test/helpers/api-unit.helper.js
@@ -40,6 +40,7 @@ export function generateRes (options = {}) {
redirect: sandbox.stub(),
render: sandbox.stub(),
send: sandbox.stub(),
+ sendFile: sandbox.stub(),
sendStatus: sandbox.stub().returnsThis(),
set: sandbox.stub(),
status: sandbox.stub().returnsThis(),
diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js
index 93ceb51922..5dae94ce4b 100644
--- a/test/helpers/content.helper.js
+++ b/test/helpers/content.helper.js
@@ -7,13 +7,13 @@ i18n.translations = translations;
export const STRING_ERROR_MSG = /^Error processing the string ".*". Please see Help > Report a Bug.$/;
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
-export function expectValidTranslationString (attribute) {
- expect(attribute).to.be.a('function');
+export function expectValidTranslationString (attribute, contextKey) {
+ expect(attribute, contextKey).to.be.a('function');
const translatedString = attribute();
- expect(translatedString.trim()).to.not.be.empty;
- expect(translatedString).to.not.contain('function func(lang)');
- expect(translatedString).to.not.eql(STRING_ERROR_MSG);
- expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG);
+ expect(translatedString.trim(), contextKey).to.not.be.empty;
+ expect(translatedString, contextKey).to.not.contain('function func(lang)');
+ expect(translatedString, contextKey).to.not.eql(STRING_ERROR_MSG);
+ expect(translatedString, contextKey).to.not.match(STRING_DOES_NOT_EXIST_MSG);
}
diff --git a/website/client/package-lock.json b/website/client/package-lock.json
index 115c37a0f8..017f4c2aa9 100644
--- a/website/client/package-lock.json
+++ b/website/client/package-lock.json
@@ -16,12 +16,12 @@
"@vue/cli-service": "^5.0.8",
"@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^8.21.3",
+ "assert": "^2.1.0",
"axios": "^0.28.0",
"axios-progress-bar": "^1.2.0",
"babel-eslint": "^10.1.0",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.23.1",
- "chai": "^5.1.0",
"core-js": "^3.33.1",
"dompurify": "^3.0.3",
"eslint": "7.32.0",
@@ -30,16 +30,18 @@
"eslint-plugin-vue": "7.20.0",
"habitica-markdown": "^3.0.0",
"hellojs": "^1.20.0",
- "inspectpack": "^4.7.1",
"intro.js": "^7.2.0",
"jquery": "^3.7.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
+ "moment-locales-webpack-plugin": "^1.2.0",
"nconf": "^0.12.1",
"sass": "^1.63.4",
"sass-loader": "^14.1.1",
+ "sinon": "^17.0.1",
"smartbanner.js": "^1.19.3",
"stopword": "^2.0.8",
+ "timers-browserify": "^2.0.12",
"uuid": "^9.0.1",
"validator": "^13.9.0",
"vue": "^2.7.10",
@@ -49,11 +51,15 @@
"vue-template-babel-compiler": "^2.0.0",
"vue-template-compiler": "^2.7.10",
"vuedraggable": "^2.24.3",
- "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
- "webpack": "^5.89.0"
+ "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0"
},
"devDependencies": {
- "@babel/plugin-proposal-optional-chaining": "^7.21.0"
+ "@babel/plugin-proposal-optional-chaining": "^7.21.0",
+ "babel-plugin-lodash": "^3.3.4",
+ "chai": "^5.1.0",
+ "inspectpack": "^4.7.1",
+ "terser-webpack-plugin": "^5.3.10",
+ "webpack": "^5.89.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -2120,6 +2126,45 @@
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz",
+ "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@sinonjs/samsam": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz",
+ "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==",
+ "dependencies": {
+ "@sinonjs/commons": "^2.0.0",
+ "lodash.get": "^4.4.2",
+ "type-detect": "^4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "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=="
+ },
"node_modules/@soda/friendly-errors-webpack-plugin": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz",
@@ -3601,10 +3646,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assert": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
+ }
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
"engines": {
"node": ">=12"
}
@@ -3683,9 +3741,9 @@
}
},
"node_modules/axios": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz",
- "integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz",
+ "integrity": "sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -3759,6 +3817,19 @@
"object.assign": "^4.1.0"
}
},
+ "node_modules/babel-plugin-lodash": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz",
+ "integrity": "sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.0.0-beta.49",
+ "@babel/types": "^7.0.0-beta.49",
+ "glob": "^7.1.1",
+ "lodash": "^4.17.10",
+ "require-package-name": "^2.0.1"
+ }
+ },
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz",
@@ -4063,13 +4134,18 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
- "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.1",
- "set-function-length": "^1.1.1"
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -4142,12 +4218,13 @@
}
},
"node_modules/chai": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.0.tgz",
- "integrity": "sha512-kDZ7MZyM6Q1DhR9jy7dalKohXQ2yrlXkk59CR52aRKxJrobmlBNqnFQxX9xOX8w+4mz8SYlKJa/7D7ddltFXCw==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
+ "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
+ "dev": true,
"dependencies": {
"assertion-error": "^2.0.1",
- "check-error": "^2.0.0",
+ "check-error": "^2.1.1",
"deep-eql": "^5.0.1",
"loupe": "^3.1.0",
"pathval": "^2.0.0"
@@ -4170,9 +4247,10 @@
}
},
"node_modules/check-error": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.0.0.tgz",
- "integrity": "sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
"engines": {
"node": ">= 16"
}
@@ -5046,6 +5124,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz",
"integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==",
+ "dev": true,
"engines": {
"node": ">=6"
}
@@ -5141,16 +5220,19 @@
}
},
"node_modules/define-data-property": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
- "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dependencies": {
- "get-intrinsic": "^1.2.1",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0"
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
@@ -5521,6 +5603,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
@@ -6929,7 +7030,8 @@
"node_modules/fp-ts": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
- "integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA=="
+ "integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
+ "dev": true
},
"node_modules/fraction.js": {
"version": "4.3.7",
@@ -6975,19 +7077,6 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -7046,20 +7135,25 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
"engines": {
"node": "*"
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
- "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": {
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -7253,11 +7347,11 @@
}
},
"node_modules/has-property-descriptors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
- "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dependencies": {
- "get-intrinsic": "^1.2.2"
+ "es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7660,6 +7754,7 @@
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/inspectpack/-/inspectpack-4.7.1.tgz",
"integrity": "sha512-XoDJbKSM9I2KA+8+OLFJHm8m4NM2pMEgsDD2hze6swVfynEed9ngCx36mRR+otzOsskwnxIZWXjI23FTW1uHqA==",
+ "dev": true,
"dependencies": {
"chalk": "^4.1.0",
"fp-ts": "^2.6.1",
@@ -7680,6 +7775,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -7694,6 +7790,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -7709,6 +7806,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -7719,12 +7817,14 @@
"node_modules/inspectpack/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"node_modules/inspectpack/node_modules/has-flag": {
"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"
}
@@ -7733,6 +7833,7 @@
"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"
},
@@ -7770,6 +7871,7 @@
"version": "2.2.21",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz",
"integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==",
+ "dev": true,
"peerDependencies": {
"fp-ts": "^2.5.0"
}
@@ -7778,6 +7880,7 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/io-ts-reporters/-/io-ts-reporters-1.2.2.tgz",
"integrity": "sha512-igASwWWkDY757OutNcM6zTtdJf/eTZYkoe2ymsX2qpm5bKZLo74FJYjsCtMQOEdY7dRHLLEulCyFQwdN69GBCg==",
+ "dev": true,
"peerDependencies": {
"fp-ts": "^2.0.2",
"io-ts": "^2.0.0"
@@ -7791,6 +7894,21 @@
"node": ">= 10"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -7931,6 +8049,20 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -7950,6 +8082,21 @@
"node": ">=8"
}
},
+ "node_modules/is-nan": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
+ "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -8335,6 +8482,11 @@
"graceful-fs": "^4.1.6"
}
},
+ "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=="
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -8470,6 +8622,16 @@
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
"integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA=="
},
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="
+ },
+ "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=="
+ },
"node_modules/lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@@ -8682,9 +8844,10 @@
}
},
"node_modules/loupe": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz",
- "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
+ "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==",
+ "dev": true,
"dependencies": {
"get-func-name": "^2.0.1"
}
@@ -9447,6 +9610,18 @@
"node": "*"
}
},
+ "node_modules/moment-locales-webpack-plugin": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.2.0.tgz",
+ "integrity": "sha512-QAi5v0OlPUP7GXviKMtxnpBAo8WmTHrUNN7iciAhNOEAd9evCOvuN0g1N7ThIg3q11GLCkjY1zQ2saRcf/43nQ==",
+ "dependencies": {
+ "lodash.difference": "^4.5.0"
+ },
+ "peerDependencies": {
+ "moment": "^2.8.0",
+ "webpack": "^1 || ^2 || ^3 || ^4 || ^5"
+ }
+ },
"node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
@@ -9530,6 +9705,23 @@
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
+ "node_modules/nise": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz",
+ "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/text-encoding": "^0.7.2",
+ "just-extend": "^6.2.0",
+ "path-to-regexp": "^6.2.1"
+ }
+ },
+ "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=="
+ },
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -9693,6 +9885,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -10135,6 +10342,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "dev": true,
"engines": {
"node": ">= 14.16"
}
@@ -10159,6 +10367,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -11275,6 +11484,12 @@
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
+ "node_modules/require-package-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
+ "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==",
+ "dev": true
+ },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -11434,9 +11649,9 @@
}
},
"node_modules/sass-loader": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz",
- "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==",
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz",
+ "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==",
"dependencies": {
"neo-async": "^2.6.2"
},
@@ -11533,7 +11748,8 @@
"node_modules/semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
- "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true
},
"node_modules/send": {
"version": "0.18.0",
@@ -11674,15 +11890,16 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
- "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dependencies": {
- "define-data-property": "^1.1.1",
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.2",
+ "get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.1"
+ "has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -11701,6 +11918,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -11762,6 +11984,50 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
+ "node_modules/sinon": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz",
+ "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/samsam": "^8.0.0",
+ "diff": "^5.1.0",
+ "nise": "^5.1.5",
+ "supports-color": "^7.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sinon"
+ }
+ },
+ "node_modules/sinon/node_modules/diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/sinon/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sinon/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/sirv": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
@@ -12261,15 +12527,15 @@
}
},
"node_modules/terser-webpack-plugin": {
- "version": "5.3.9",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
- "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
+ "version": "5.3.10",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
+ "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.17",
+ "@jridgewell/trace-mapping": "^0.3.20",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.1",
- "terser": "^5.16.8"
+ "terser": "^5.26.0"
},
"engines": {
"node": ">= 10.13.0"
@@ -12404,6 +12670,17 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
+ "node_modules/timers-browserify": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
+ "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
+ "dependencies": {
+ "setimmediate": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -12515,6 +12792,14 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-detect": {
+ "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==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/type-fest": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
@@ -12718,6 +13003,18 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/util": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+ "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "is-typed-array": "^1.1.3",
+ "which-typed-array": "^1.1.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/website/client/package.json b/website/client/package.json
index b7323b01f9..9d785ec6d3 100644
--- a/website/client/package.json
+++ b/website/client/package.json
@@ -18,12 +18,12 @@
"@vue/cli-service": "^5.0.8",
"@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^8.21.3",
+ "assert": "^2.1.0",
"axios": "^0.28.0",
"axios-progress-bar": "^1.2.0",
"babel-eslint": "^10.1.0",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.23.1",
- "chai": "^5.1.0",
"core-js": "^3.33.1",
"dompurify": "^3.0.3",
"eslint": "7.32.0",
@@ -32,16 +32,18 @@
"eslint-plugin-vue": "7.20.0",
"habitica-markdown": "^3.0.0",
"hellojs": "^1.20.0",
- "inspectpack": "^4.7.1",
"intro.js": "^7.2.0",
"jquery": "^3.7.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
+ "moment-locales-webpack-plugin": "^1.2.0",
"nconf": "^0.12.1",
"sass": "^1.63.4",
"sass-loader": "^14.1.1",
+ "sinon": "^17.0.1",
"smartbanner.js": "^1.19.3",
"stopword": "^2.0.8",
+ "timers-browserify": "^2.0.12",
"uuid": "^9.0.1",
"validator": "^13.9.0",
"vue": "^2.7.10",
@@ -51,10 +53,14 @@
"vue-template-babel-compiler": "^2.0.0",
"vue-template-compiler": "^2.7.10",
"vuedraggable": "^2.24.3",
- "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
- "webpack": "^5.89.0"
+ "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0"
},
"devDependencies": {
- "@babel/plugin-proposal-optional-chaining": "^7.21.0"
+ "@babel/plugin-proposal-optional-chaining": "^7.21.0",
+ "babel-plugin-lodash": "^3.3.4",
+ "chai": "^5.1.0",
+ "inspectpack": "^4.7.1",
+ "terser-webpack-plugin": "^5.3.10",
+ "webpack": "^5.89.0"
}
}
diff --git a/website/client/public/static/npc/normal/customizations_background.png b/website/client/public/static/npc/normal/customizations_background.png
new file mode 100644
index 0000000000..0d1cc9c273
Binary files /dev/null and b/website/client/public/static/npc/normal/customizations_background.png differ
diff --git a/website/client/public/static/npc/normal/customizations_npc.png b/website/client/public/static/npc/normal/customizations_npc.png
new file mode 100644
index 0000000000..6f0d483062
Binary files /dev/null and b/website/client/public/static/npc/normal/customizations_npc.png differ
diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css
index 76665016b4..c64f12c9be 100644
--- a/website/client/src/assets/css/sprites/spritesmith-main.css
+++ b/website/client/src/assets/css/sprites/spritesmith-main.css
@@ -1875,6 +1875,11 @@
width: 141px;
height: 147px;
}
+.background_river_bottom {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_river_bottom.png');
+ width: 141px;
+ height: 147px;
+}
.background_river_of_lava {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_river_of_lava.png');
width: 141px;
@@ -1940,6 +1945,11 @@
width: 141px;
height: 147px;
}
+.background_shell_gate {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_shell_gate.png');
+ width: 141px;
+ height: 147px;
+}
.background_shimmering_ice_prism {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_shimmering_ice_prism.png');
width: 141px;
@@ -3768,6 +3778,11 @@
width: 68px;
height: 68px;
}
+.icon_background_river_bottom {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_river_bottom.png');
+ width: 68px;
+ height: 68px;
+}
.icon_background_river_of_lava {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_river_of_lava.png');
width: 68px;
@@ -3833,6 +3848,11 @@
width: 68px;
height: 68px;
}
+.icon_background_shell_gate {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_shell_gate.png');
+ width: 68px;
+ height: 68px;
+}
.icon_background_shimmering_ice_prism {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_shimmering_ice_prism.png');
width: 68px;
@@ -19992,11 +20012,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_1_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_1_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_1_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_1_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_pblue.png');
width: 68px;
@@ -20082,11 +20112,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_1_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_1_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_1_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_1_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_1_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_1_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_1_white.png');
width: 68px;
@@ -20187,11 +20237,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_2_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_2_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_2_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_2_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_pblue.png');
width: 68px;
@@ -20277,11 +20337,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_2_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_2_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_2_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_2_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_2_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_2_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_2_white.png');
width: 68px;
@@ -20382,11 +20462,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_3_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_3_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_3_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_3_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_pblue.png');
width: 68px;
@@ -20472,11 +20562,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_3_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_3_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_3_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_3_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_3_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_3_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_3_white.png');
width: 68px;
@@ -20577,11 +20687,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_4_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_4_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_4_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_4_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_pblue.png');
width: 68px;
@@ -20667,11 +20787,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_bangs_4_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_4_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_4_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_bangs_4_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_bangs_4_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_bangs_4_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_bangs_4_white.png');
width: 68px;
@@ -20772,11 +20912,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_10_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_10_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_10_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_10_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_pblue.png');
width: 68px;
@@ -20862,11 +21012,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_10_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_10_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_10_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_10_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_10_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_10_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_10_white.png');
width: 68px;
@@ -20967,11 +21137,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_11_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_11_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_11_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_11_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_pblue.png');
width: 68px;
@@ -21057,11 +21237,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_11_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_11_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_11_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_11_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_11_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_11_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_11_white.png');
width: 68px;
@@ -21162,11 +21362,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_12_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_12_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_12_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_12_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_pblue.png');
width: 68px;
@@ -21252,11 +21462,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_12_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_12_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_12_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_12_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_12_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_12_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_12_white.png');
width: 68px;
@@ -21357,11 +21587,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_13_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_13_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_13_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_13_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_pblue.png');
width: 68px;
@@ -21447,11 +21687,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_13_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_13_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_13_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_13_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_13_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_13_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_13_white.png');
width: 68px;
@@ -21552,11 +21812,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_14_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_14_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_14_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_14_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_pblue.png');
width: 68px;
@@ -21642,11 +21912,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_14_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_14_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_14_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_14_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_14_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_14_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_14_white.png');
width: 68px;
@@ -21747,11 +22037,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_15_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_15_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_15_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_15_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_pblue.png');
width: 68px;
@@ -21837,11 +22137,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_15_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_15_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_15_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_15_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_15_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_15_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_15_white.png');
width: 68px;
@@ -21942,11 +22262,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_16_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_16_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_16_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_16_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_pblue.png');
width: 68px;
@@ -22032,11 +22362,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_16_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_16_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_16_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_16_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_16_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_16_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_16_white.png');
width: 68px;
@@ -22137,11 +22487,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_17_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_17_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_17_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_17_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_pblue.png');
width: 68px;
@@ -22227,11 +22587,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_17_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_17_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_17_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_17_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_17_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_17_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_17_white.png');
width: 68px;
@@ -22332,11 +22712,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_18_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_18_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_18_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_18_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_pblue.png');
width: 68px;
@@ -22422,11 +22812,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_18_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_18_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_18_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_18_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_18_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_18_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_18_white.png');
width: 68px;
@@ -22527,11 +22937,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_19_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_19_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_19_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_19_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_pblue.png');
width: 68px;
@@ -22617,11 +23037,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_19_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_19_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_19_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_19_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_19_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_19_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_19_white.png');
width: 68px;
@@ -22722,11 +23162,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_1_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_1_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_1_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_1_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_pblue.png');
width: 68px;
@@ -22812,11 +23262,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_1_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_1_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_1_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_1_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_1_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_1_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_1_white.png');
width: 68px;
@@ -22917,11 +23387,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_20_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_20_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_20_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_20_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_pblue.png');
width: 68px;
@@ -23007,11 +23487,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_20_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_20_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_20_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_20_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_20_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_20_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_20_white.png');
width: 68px;
@@ -23112,11 +23612,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_2_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_2_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_2_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_2_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_pblue.png');
width: 68px;
@@ -23202,11 +23712,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_2_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_2_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_2_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_2_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_2_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_2_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_2_white.png');
width: 68px;
@@ -23307,11 +23837,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_3_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_3_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_3_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_3_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_pblue.png');
width: 68px;
@@ -23397,11 +23937,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_3_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_3_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_3_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_3_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_3_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_3_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_3_white.png');
width: 68px;
@@ -23502,11 +24062,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_4_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_4_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_4_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_4_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_pblue.png');
width: 68px;
@@ -23592,11 +24162,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_4_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_4_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_4_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_4_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_4_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_4_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_4_white.png');
width: 68px;
@@ -23697,11 +24287,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_5_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_5_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_5_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_5_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_pblue.png');
width: 68px;
@@ -23787,11 +24387,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_5_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_5_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_5_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_5_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_5_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_5_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_5_white.png');
width: 68px;
@@ -23892,11 +24512,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_6_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_6_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_6_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_6_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_pblue.png');
width: 68px;
@@ -23982,11 +24612,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_6_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_6_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_6_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_6_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_6_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_6_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_6_white.png');
width: 68px;
@@ -24087,11 +24737,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_7_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_7_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_7_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_7_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_pblue.png');
width: 68px;
@@ -24177,11 +24837,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_7_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_7_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_7_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_7_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_7_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_7_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_7_white.png');
width: 68px;
@@ -24282,11 +24962,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_8_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_8_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_8_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_8_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_pblue.png');
width: 68px;
@@ -24372,11 +25062,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_8_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_8_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_8_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_8_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_8_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_8_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_8_white.png');
width: 68px;
@@ -24477,11 +25187,21 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_9_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_lava.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_9_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_midnight.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_9_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_orchid.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_9_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_pblue.png');
width: 68px;
@@ -24567,11 +25287,31 @@
width: 68px;
height: 68px;
}
+.icon_hair_base_9_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_9_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_9_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_snowy.png');
width: 68px;
height: 68px;
}
+.icon_hair_base_9_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.icon_hair_base_9_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_sunset.png');
+ width: 68px;
+ height: 68px;
+}
.icon_hair_base_9_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_base_9_white.png');
width: 68px;
@@ -24762,6 +25502,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_1_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_lava.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_lava {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_lava.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_1_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_midnight.png');
width: 68px;
@@ -24773,6 +25524,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_1_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_orchid.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_orchid {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_orchid.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_1_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_pblue.png');
width: 68px;
@@ -24960,6 +25722,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_1_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_sandnsea {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sandnsea.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_1_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_seasprite {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_seasprite.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_1_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_snowy.png');
width: 68px;
@@ -24971,6 +25755,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_1_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_sunlitwaves {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sunlitwaves.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_1_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sunset.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_1_sunset {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_sunset.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_1_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_1_white.png');
width: 68px;
@@ -25191,6 +25997,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_2_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_lava.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_lava {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_lava.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_2_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_midnight.png');
width: 68px;
@@ -25202,6 +26019,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_2_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_orchid.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_orchid {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_orchid.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_2_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_pblue.png');
width: 68px;
@@ -25389,6 +26217,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_2_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_sandnsea {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sandnsea.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_2_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_seasprite {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_seasprite.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_2_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_snowy.png');
width: 68px;
@@ -25400,6 +26250,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_2_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_sunlitwaves {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sunlitwaves.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_2_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sunset.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_2_sunset {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_sunset.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_2_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_2_white.png');
width: 68px;
@@ -25620,6 +26492,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_3_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_lava.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_lava {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_lava.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_3_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_midnight.png');
width: 68px;
@@ -25631,6 +26514,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_3_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_orchid.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_orchid {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_orchid.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_3_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_pblue.png');
width: 68px;
@@ -25818,6 +26712,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_3_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_sandnsea {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sandnsea.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_3_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_seasprite {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_seasprite.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_3_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_snowy.png');
width: 68px;
@@ -25829,6 +26745,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_beard_3_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_sunlitwaves {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sunlitwaves.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_beard_3_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sunset.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_beard_3_sunset {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_sunset.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_beard_3_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_beard_3_white.png');
width: 68px;
@@ -26225,6 +27163,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_1_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_lava.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_lava {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_lava.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_1_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_midnight.png');
width: 68px;
@@ -26236,6 +27185,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_1_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_orchid.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_orchid {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_orchid.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_1_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_pblue.png');
width: 68px;
@@ -26423,6 +27383,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_1_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_sandnsea {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sandnsea.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_mustache_1_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_seasprite {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_seasprite.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_1_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_snowy.png');
width: 68px;
@@ -26434,6 +27416,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_1_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_sunlitwaves {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sunlitwaves.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_mustache_1_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sunset.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_1_sunset {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_sunset.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_1_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_1_white.png');
width: 68px;
@@ -26654,6 +27658,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_2_lava {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_lava.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_lava {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_lava.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_2_midnight {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_midnight.png');
width: 68px;
@@ -26665,6 +27680,17 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_2_orchid {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_orchid.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_orchid {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_orchid.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_2_pblue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_pblue.png');
width: 68px;
@@ -26852,6 +27878,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_2_sandnsea {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sandnsea.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_sandnsea {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sandnsea.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_mustache_2_seasprite {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_seasprite.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_seasprite {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_seasprite.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_2_snowy {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_snowy.png');
width: 68px;
@@ -26863,6 +27911,28 @@
width: 60px;
height: 60px;
}
+.icon_hair_mustache_2_sunlitwaves {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sunlitwaves.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_sunlitwaves {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sunlitwaves.png');
+ width: 60px;
+ height: 60px;
+}
+.icon_hair_mustache_2_sunset {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sunset.png');
+ width: 68px;
+ height: 68px;
+}
+.customize-option.icon_hair_mustache_2_sunset {
+ background-position: -25px -15px;
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_sunset.png');
+ width: 60px;
+ height: 60px;
+}
.icon_hair_mustache_2_white {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_hair_mustache_2_white.png');
width: 68px;
@@ -30382,6 +31452,11 @@
width: 90px;
height: 90px;
}
+.broad_armor_armoire_corsairsCoatAndCape {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_corsairsCoatAndCape.png');
+ width: 114px;
+ height: 90px;
+}
.broad_armor_armoire_coverallsOfBookbinding {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_coverallsOfBookbinding.png');
width: 114px;
@@ -30947,6 +32022,11 @@
width: 114px;
height: 90px;
}
+.head_armoire_corsairsBandana {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_corsairsBandana.png');
+ width: 114px;
+ height: 90px;
+}
.head_armoire_crownOfDiamonds {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_crownOfDiamonds.png');
width: 114px;
@@ -31812,6 +32892,11 @@
width: 68px;
height: 68px;
}
+.shop_armor_armoire_corsairsCoatAndCape {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_corsairsCoatAndCape.png');
+ width: 68px;
+ height: 68px;
+}
.shop_armor_armoire_coverallsOfBookbinding {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_coverallsOfBookbinding.png');
width: 68px;
@@ -32437,6 +33522,11 @@
width: 68px;
height: 68px;
}
+.shop_head_armoire_corsairsBandana {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_corsairsBandana.png');
+ width: 68px;
+ height: 68px;
+}
.shop_head_armoire_crownOfDiamonds {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_crownOfDiamonds.png');
width: 68px;
@@ -33297,6 +34387,11 @@
width: 68px;
height: 68px;
}
+.shop_weapon_armoire_corsairsBlade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_corsairsBlade.png');
+ width: 68px;
+ height: 68px;
+}
.shop_weapon_armoire_crystalCrescentStaff {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_crystalCrescentStaff.png');
width: 68px;
@@ -33812,6 +34907,11 @@
width: 90px;
height: 90px;
}
+.slim_armor_armoire_corsairsCoatAndCape {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_corsairsCoatAndCape.png');
+ width: 114px;
+ height: 90px;
+}
.slim_armor_armoire_coverallsOfBookbinding {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_coverallsOfBookbinding.png');
width: 114px;
@@ -34327,6 +35427,11 @@
width: 90px;
height: 90px;
}
+.weapon_armoire_corsairsBlade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_corsairsBlade.png');
+ width: 114px;
+ height: 90px;
+}
.weapon_armoire_crystalCrescentStaff {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_crystalCrescentStaff.png');
width: 90px;
@@ -40972,6 +42077,36 @@
width: 114px;
height: 90px;
}
+.broad_armor_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202407.png');
+ width: 117px;
+ height: 120px;
+}
+.head_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202407.png');
+ width: 117px;
+ height: 120px;
+}
+.shop_armor_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202407.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_head_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202407.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_set_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202407.png');
+ width: 68px;
+ height: 68px;
+}
+.slim_armor_mystery_202407 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202407.png');
+ width: 117px;
+ height: 120px;
+}
.broad_armor_mystery_301404 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
width: 90px;
@@ -43407,6 +44542,26 @@
width: 114px;
height: 120px;
}
+.broad_armor_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2024Healer.png');
+ width: 114px;
+ height: 90px;
+}
+.broad_armor_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2024Mage.png');
+ width: 114px;
+ height: 117px;
+}
+.broad_armor_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2024Rogue.png');
+ width: 114px;
+ height: 117px;
+}
+.broad_armor_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2024Warrior.png');
+ width: 114px;
+ height: 117px;
+}
.broad_armor_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summerHealer.png');
width: 90px;
@@ -43617,6 +44772,26 @@
width: 114px;
height: 120px;
}
+.head_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2024Healer.png');
+ width: 114px;
+ height: 90px;
+}
+.head_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2024Mage.png');
+ width: 114px;
+ height: 117px;
+}
+.head_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2024Rogue.png');
+ width: 114px;
+ height: 117px;
+}
+.head_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2024Warrior.png');
+ width: 114px;
+ height: 117px;
+}
.head_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summerHealer.png');
width: 90px;
@@ -43777,6 +44952,21 @@
width: 114px;
height: 120px;
}
+.shield_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2024Healer.png');
+ width: 114px;
+ height: 90px;
+}
+.shield_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2024Rogue.png');
+ width: 114px;
+ height: 117px;
+}
+.shield_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2024Warrior.png');
+ width: 114px;
+ height: 117px;
+}
.shield_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summerHealer.png');
width: 90px;
@@ -43972,6 +45162,26 @@
width: 68px;
height: 68px;
}
+.shop_armor_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2024Healer.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_armor_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2024Mage.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_armor_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2024Rogue.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_armor_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2024Warrior.png');
+ width: 68px;
+ height: 68px;
+}
.shop_armor_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summerHealer.png');
width: 68px;
@@ -44022,6 +45232,11 @@
width: 68px;
height: 68px;
}
+.shop_eyewear_mystery_202406 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_mystery_202406.png');
+ width: 68px;
+ height: 68px;
+}
.shop_eyewear_special_summerRogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_special_summerRogue.png');
width: 68px;
@@ -44212,6 +45427,26 @@
width: 68px;
height: 68px;
}
+.shop_head_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2024Healer.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_head_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2024Mage.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_head_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2024Rogue.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_head_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2024Warrior.png');
+ width: 68px;
+ height: 68px;
+}
.shop_head_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summerHealer.png');
width: 68px;
@@ -44372,6 +45607,21 @@
width: 68px;
height: 68px;
}
+.shop_shield_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2024Healer.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_shield_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2024Rogue.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_shield_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2024Warrior.png');
+ width: 68px;
+ height: 68px;
+}
.shop_shield_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summerHealer.png');
width: 68px;
@@ -44567,6 +45817,26 @@
width: 68px;
height: 68px;
}
+.shop_weapon_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2024Healer.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_weapon_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2024Mage.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_weapon_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2024Rogue.png');
+ width: 68px;
+ height: 68px;
+}
+.shop_weapon_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2024Warrior.png');
+ width: 68px;
+ height: 68px;
+}
.shop_weapon_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summerHealer.png');
width: 68px;
@@ -44587,6 +45857,11 @@
width: 68px;
height: 68px;
}
+.slim_armor_mystery_202406 {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202406.png');
+ width: 114px;
+ height: 90px;
+}
.slim_armor_special_summer2015Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2015Healer.png');
width: 90px;
@@ -44767,6 +46042,26 @@
width: 114px;
height: 120px;
}
+.slim_armor_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2024Healer.png');
+ width: 114px;
+ height: 90px;
+}
+.slim_armor_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2024Mage.png');
+ width: 114px;
+ height: 117px;
+}
+.slim_armor_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2024Rogue.png');
+ width: 114px;
+ height: 117px;
+}
+.slim_armor_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2024Warrior.png');
+ width: 114px;
+ height: 117px;
+}
.slim_armor_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summerHealer.png');
width: 90px;
@@ -44967,6 +46262,26 @@
width: 114px;
height: 120px;
}
+.weapon_special_summer2024Healer {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2024Healer.png');
+ width: 114px;
+ height: 90px;
+}
+.weapon_special_summer2024Mage {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2024Mage.png');
+ width: 114px;
+ height: 117px;
+}
+.weapon_special_summer2024Rogue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2024Rogue.png');
+ width: 114px;
+ height: 117px;
+}
+.weapon_special_summer2024Warrior {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2024Warrior.png');
+ width: 114px;
+ height: 117px;
+}
.weapon_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summerHealer.png');
width: 90px;
@@ -48895,6 +50210,11 @@
width: 28px;
height: 28px;
}
+.icon_pet_veteran_cactus {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_cactus.png');
+ width: 28px;
+ height: 28px;
+}
.icon_pet_veteran_dragon {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_dragon.png');
width: 28px;
@@ -49235,6 +50555,11 @@
width: 219px;
height: 219px;
}
+.quest_chameleon {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_chameleon.png');
+ width: 216px;
+ height: 216px;
+}
.quest_cheetah {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_cheetah.png');
width: 219px;
@@ -49945,6 +51270,11 @@
width: 68px;
height: 68px;
}
+.inventory_quest_scroll_chameleon {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_chameleon.png');
+ width: 68px;
+ height: 68px;
+}
.inventory_quest_scroll_cheetah {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_cheetah.png');
width: 68px;
@@ -50820,6 +52150,11 @@
width: 68px;
height: 68px;
}
+.Pet_Egg_Chameleon {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_Egg_Chameleon.png');
+ width: 68px;
+ height: 68px;
+}
.Pet_Egg_Cheetah {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_Egg_Cheetah.png');
width: 68px;
@@ -51615,6 +52950,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_BearCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_BearCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-Moonglow.png');
width: 105px;
@@ -52050,6 +53390,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_Cactus-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_Cactus-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-Moonglow.png');
width: 105px;
@@ -52210,6 +53555,56 @@
width: 105px;
height: 105px;
}
+.Mount_Body_Chameleon-Base {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Base.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-CottonCandyBlue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-CottonCandyBlue.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-CottonCandyPink {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-CottonCandyPink.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Desert {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Desert.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Golden {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Golden.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Red {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Red.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Shade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Shade.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Skeleton {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Skeleton.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-White {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-White.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Body_Chameleon-Zombie {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Chameleon-Zombie.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_Cheetah-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cheetah-Base.png');
width: 105px;
@@ -52580,6 +53975,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_Dragon-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_Dragon-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Moonglow.png');
width: 105px;
@@ -53010,6 +54410,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_FlyingPig-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_FlyingPig-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-Moonglow.png');
width: 105px;
@@ -53290,6 +54695,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_Fox-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_Fox-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-Moonglow.png');
width: 105px;
@@ -54010,6 +55420,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_LionCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_LionCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-Moonglow.png');
width: 105px;
@@ -54510,6 +55925,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_PandaCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_PandaCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-Moonglow.png');
width: 105px;
@@ -55745,6 +57165,11 @@
width: 105px;
height: 105px;
}
+.Mount_Body_TigerCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Body_TigerCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-Moonglow.png');
width: 105px;
@@ -56335,6 +57760,11 @@
width: 135px;
height: 135px;
}
+.Mount_Body_Wolf-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-Koi.png');
+ width: 135px;
+ height: 135px;
+}
.Mount_Body_Wolf-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-Moonglow.png');
width: 135px;
@@ -56865,6 +58295,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_BearCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_BearCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-Moonglow.png');
width: 105px;
@@ -57300,6 +58735,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_Cactus-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_Cactus-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-Moonglow.png');
width: 105px;
@@ -57460,6 +58900,56 @@
width: 105px;
height: 105px;
}
+.Mount_Head_Chameleon-Base {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Base.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-CottonCandyBlue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-CottonCandyBlue.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-CottonCandyPink {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-CottonCandyPink.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Desert {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Desert.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Golden {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Golden.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Red {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Red.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Shade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Shade.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Skeleton {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Skeleton.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-White {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-White.png');
+ width: 105px;
+ height: 105px;
+}
+.Mount_Head_Chameleon-Zombie {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Chameleon-Zombie.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_Cheetah-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cheetah-Base.png');
width: 105px;
@@ -57830,6 +59320,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_Dragon-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_Dragon-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Moonglow.png');
width: 105px;
@@ -58260,6 +59755,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_FlyingPig-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_FlyingPig-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-Moonglow.png');
width: 105px;
@@ -58540,6 +60040,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_Fox-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_Fox-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-Moonglow.png');
width: 105px;
@@ -59260,6 +60765,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_LionCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_LionCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-Moonglow.png');
width: 105px;
@@ -59760,6 +61270,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_PandaCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_PandaCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-Moonglow.png');
width: 105px;
@@ -60995,6 +62510,11 @@
width: 105px;
height: 105px;
}
+.Mount_Head_TigerCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-Koi.png');
+ width: 105px;
+ height: 105px;
+}
.Mount_Head_TigerCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-Moonglow.png');
width: 105px;
@@ -61585,6 +63105,11 @@
width: 135px;
height: 135px;
}
+.Mount_Head_Wolf-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-Koi.png');
+ width: 135px;
+ height: 135px;
+}
.Mount_Head_Wolf-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-Moonglow.png');
width: 135px;
@@ -62120,6 +63645,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_BearCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_BearCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_BearCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_BearCub-Moonglow.png');
width: 81px;
@@ -62555,6 +64085,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_Cactus-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Cactus-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_Cactus-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Cactus-Moonglow.png');
width: 81px;
@@ -62715,6 +64250,56 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_Chameleon-Base {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Base.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-CottonCandyBlue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-CottonCandyBlue.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-CottonCandyPink {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-CottonCandyPink.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Desert {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Desert.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Golden {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Golden.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Red {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Red.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Shade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Shade.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Skeleton {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Skeleton.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-White {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-White.png');
+ width: 81px;
+ height: 99px;
+}
+.Mount_Icon_Chameleon-Zombie {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Chameleon-Zombie.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_Cheetah-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Cheetah-Base.png');
width: 81px;
@@ -63085,6 +64670,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_Dragon-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Dragon-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_Dragon-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Dragon-Moonglow.png');
width: 81px;
@@ -63515,6 +65105,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_FlyingPig-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_FlyingPig-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_FlyingPig-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_FlyingPig-Moonglow.png');
width: 81px;
@@ -63795,6 +65390,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_Fox-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Fox-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_Fox-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Fox-Moonglow.png');
width: 81px;
@@ -64520,6 +66120,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_LionCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_LionCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_LionCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_LionCub-Moonglow.png');
width: 81px;
@@ -65020,6 +66625,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_PandaCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_PandaCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_PandaCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_PandaCub-Moonglow.png');
width: 81px;
@@ -66255,6 +67865,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_TigerCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_TigerCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_TigerCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_TigerCub-Moonglow.png');
width: 81px;
@@ -66845,6 +68460,11 @@
width: 81px;
height: 99px;
}
+.Mount_Icon_Wolf-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Wolf-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Mount_Icon_Wolf-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Wolf-Moonglow.png');
width: 81px;
@@ -67390,6 +69010,11 @@
width: 81px;
height: 99px;
}
+.Pet-BearCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-BearCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Moonglow.png');
width: 81px;
@@ -67850,6 +69475,11 @@
width: 81px;
height: 99px;
}
+.Pet-Cactus-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-Cactus-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Moonglow.png');
width: 81px;
@@ -68000,6 +69630,11 @@
width: 81px;
height: 99px;
}
+.Pet-Cactus-Veteran {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Veteran.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-Cactus-VirtualPet {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-VirtualPet.png');
width: 81px;
@@ -68025,6 +69660,56 @@
width: 81px;
height: 99px;
}
+.Pet-Chameleon-Base {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Base.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-CottonCandyBlue {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-CottonCandyBlue.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-CottonCandyPink {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-CottonCandyPink.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Desert {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Desert.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Golden {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Golden.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Red {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Red.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Shade {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Shade.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Skeleton {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Skeleton.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-White {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-White.png');
+ width: 81px;
+ height: 99px;
+}
+.Pet-Chameleon-Zombie {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Chameleon-Zombie.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-Cheetah-Base {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cheetah-Base.png');
width: 81px;
@@ -68410,6 +70095,11 @@
width: 81px;
height: 99px;
}
+.Pet-Dragon-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-Dragon-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Moonglow.png');
width: 81px;
@@ -68870,6 +70560,11 @@
width: 81px;
height: 99px;
}
+.Pet-FlyingPig-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-FlyingPig-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-Moonglow.png');
width: 81px;
@@ -69175,6 +70870,11 @@
width: 81px;
height: 99px;
}
+.Pet-Fox-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-Fox-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-Moonglow.png');
width: 81px;
@@ -69925,6 +71625,11 @@
width: 81px;
height: 99px;
}
+.Pet-LionCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-LionCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-Moonglow.png');
width: 81px;
@@ -70450,6 +72155,11 @@
width: 81px;
height: 99px;
}
+.Pet-PandaCub-Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-Koi.png');
+ width: 81px;
+ height: 99px;
+}
.Pet-PandaCub-Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-Moonglow.png');
width: 81px;
@@ -72660,11 +74370,6 @@
width: 68px;
height: 68px;
}
-.Pet_HatchingPotion_Fungi {
- background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Fungi.png');
- width: 68px;
- height: 68px;
-}
.Pet_HatchingPotion_Ghost {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Ghost.png');
width: 68px;
@@ -72695,6 +74400,11 @@
width: 68px;
height: 68px;
}
+.Pet_HatchingPotion_Koi {
+ background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Koi.png');
+ width: 68px;
+ height: 68px;
+}
.Pet_HatchingPotion_Moonglow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Moonglow.png');
width: 68px;
diff --git a/website/client/src/assets/scss/button.scss b/website/client/src/assets/scss/button.scss
index c45f9a8396..62eb95422f 100644
--- a/website/client/src/assets/scss/button.scss
+++ b/website/client/src/assets/scss/button.scss
@@ -217,16 +217,13 @@
.btn-show-more {
display: block;
- width: 50%;
- max-width: 448px;
- margin: 0 auto;
- margin-top: 12px;
+ width: 100%;
padding: 8px;
font-size: 14px;
line-height: 1.43;
font-weight: bold;
text-align: center;
- background: $gray-600;
+ background: $gray-500;
color: $gray-200 !important; // Otherwise it gets ignored when used on an A element
box-shadow: none;
diff --git a/website/client/src/assets/scss/icon.scss b/website/client/src/assets/scss/icon.scss
index 3ab036333a..ad57951ac6 100644
--- a/website/client/src/assets/scss/icon.scss
+++ b/website/client/src/assets/scss/icon.scss
@@ -12,7 +12,7 @@
}
&.color {
- svg path {
+ svg path, svg polygon {
fill: currentColor;
}
}
diff --git a/website/client/src/assets/scss/item.scss b/website/client/src/assets/scss/item.scss
index ab52d935d1..c9f0d65f7e 100644
--- a/website/client/src/assets/scss/item.scss
+++ b/website/client/src/assets/scss/item.scss
@@ -1,5 +1,11 @@
// TODO move to item component?
+.item, .item-wrapper, .item > div > div {
+ &:focus-visible {
+ outline: none;
+ }
+}
+
.items > div {
display: inline-block;
margin-right: 24px;
@@ -9,34 +15,22 @@
position: relative;
display: inline-block;
margin-bottom: 12px;
+
+ &:focus {
+ outline: 2px solid $purple-400;
+ border-radius: 2px;
+ }
+
+ &:hover {
+ box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
+ border-radius: 4px;
+ }
}
.items-one-line .item-wrapper {
margin-bottom: 8px;
}
-.item.pet-slot {
- // Desktop XL (1440)
- @media only screen and (min-width: 1440px){
- margin-right: 1.71em;
- }
-
- // Desktop L (1280)
- @media only screen and (min-width: 1280px) and (max-width: 1439px) {
- margin-right: 0.43em;
- }
-
- // Desktop M (1024)
- @media only screen and (min-width: 1024px) and (max-width: 1279px) {
- margin-right: 0.86em;
- }
-
- // Tablets and mobile
- @media only screen and (max-width: 1023px) {
- margin-right: 1.71em;
- }
-}
-
.item {
position: relative;
width: 94px;
@@ -56,11 +50,6 @@
background: $purple-500;
}
- &:hover {
- box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
- border-color: $purple-400;
- }
-
&.highlight {
box-shadow: 0 0 8px 8px rgba($black, 0.16), 0 5px 10px 0 rgba($black, 0.12) !important;
}
@@ -70,9 +59,15 @@
}
}
-.flat .item {
- box-shadow: none;
- border: none;
+.flat {
+ .item {
+ box-shadow: none;
+ border: none;
+ }
+
+ .item-wrapper:hover {
+ box-shadow: none;
+ }
}
.bordered-item .item {
diff --git a/website/client/src/assets/scss/shops.scss b/website/client/src/assets/scss/shops.scss
new file mode 100644
index 0000000000..afcb8353a4
--- /dev/null
+++ b/website/client/src/assets/scss/shops.scss
@@ -0,0 +1,90 @@
+.featured-label {
+ margin: 24px auto;
+}
+
+.group {
+ display: inline-block;
+ width: 33%;
+ margin-bottom: 24px;
+
+ .items {
+ border-radius: 2px;
+ background-color: #edecee;
+ display: inline-block;
+ padding: 8px;
+ }
+
+ .item-wrapper {
+ margin-bottom: 0;
+ }
+
+ .items > div:not(:last-of-type) {
+ margin-right: 16px;
+ }
+}
+
+.timeTravelers {
+ .standard-page {
+ position: relative;
+ }
+
+ .badge-pin:not(.pinned) {
+ display: none;
+ }
+
+ .item:hover .badge-pin {
+ display: block;
+ }
+
+ .avatar {
+ cursor: default;
+ margin: 0 auto;
+ }
+
+ .featuredItems {
+ height: 192px;
+
+ .background {
+ background-repeat: repeat-x;
+
+ width: 100%;
+ position: absolute;
+
+ top: 0;
+ left: 0;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+ .background-open, .background-closed {
+ height: 216px;
+ }
+
+ .content {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .npc {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 216px;
+ background-repeat: no-repeat;
+
+ &.closed {
+ background-repeat: no-repeat;
+ }
+
+ .featured-label {
+ position: absolute;
+ bottom: -14px;
+ margin: 0;
+ left: 79px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/website/client/src/components/admin-panel/user-support/customizationsOwned.vue b/website/client/src/components/admin-panel/user-support/customizationsOwned.vue
index 9b244efe60..136baf2afb 100644
--- a/website/client/src/components/admin-panel/user-support/customizationsOwned.vue
+++ b/website/client/src/components/admin-panel/user-support/customizationsOwned.vue
@@ -158,7 +158,6 @@ function collateItemData (self) {
if (
// ignore items the user owns because we captured them above:
!(key in ownedItems)
- && allItems[key].price > 0
) {
const item = allItems[key];
itemData.push({
diff --git a/website/client/src/components/admin-panel/user-support/itemsOwned.vue b/website/client/src/components/admin-panel/user-support/itemsOwned.vue
index 0385857eca..c637596766 100644
--- a/website/client/src/components/admin-panel/user-support/itemsOwned.vue
+++ b/website/client/src/components/admin-panel/user-support/itemsOwned.vue
@@ -282,20 +282,16 @@ export default {
item.modified = true;
// for non-integer items, toggle through the allowed values:
- if (item.itemType === 'gear') {
- // Allowed starting values are true, false, and '' (never owned)
- // Allowed values to switch to are true and false
- item.value = !item.value;
- } else if (item.itemType === 'mounts') {
- // Allowed starting values are true, null, and "never owned"
- // Allowed values to switch to are true and null
- if (item.value === true) {
- item.value = null;
+ if (item.itemType === 'gear' || item.itemType === 'mounts') {
+ // Allowed starting values are true, false, and undefined (never owned)
+ if (item.value && item.value !== '') {
+ item.value = false;
+ } else if (typeof item.value === 'boolean') {
+ item.value = '';
} else {
item.value = true;
}
}
- // @TODO add a delete option
},
},
};
diff --git a/website/client/src/components/admin-panel/user-support/userProfile.vue b/website/client/src/components/admin-panel/user-support/userProfile.vue
index 2cfe84cc6d..adb100382e 100644
--- a/website/client/src/components/admin-panel/user-support/userProfile.vue
+++ b/website/client/src/components/admin-panel/user-support/userProfile.vue
@@ -28,15 +28,15 @@
+
+
@@ -67,13 +34,13 @@
-
-
diff --git a/website/client/src/components/avatarModal/hair-settings.vue b/website/client/src/components/avatarModal/hair-settings.vue
index 3d41a52ee1..39208bb37f 100644
--- a/website/client/src/components/avatarModal/hair-settings.vue
+++ b/website/client/src/components/avatarModal/hair-settings.vue
@@ -1,7 +1,8 @@
-
+
+
+
-
-
+
+
+ {{ $t('noItemsOwned') }}
+
+
+
+
-
-
diff --git a/website/client/src/components/avatarModal/skin-settings.vue b/website/client/src/components/avatarModal/skin-settings.vue
index 3fff874fa5..e61803fdda 100644
--- a/website/client/src/components/avatarModal/skin-settings.vue
+++ b/website/client/src/components/avatarModal/skin-settings.vue
@@ -1,7 +1,8 @@
diff --git a/website/client/src/components/header/menu.vue b/website/client/src/components/header/menu.vue
index 900cf0ec93..fb9db1f1bf 100644
--- a/website/client/src/components/header/menu.vue
+++ b/website/client/src/components/header/menu.vue
@@ -134,6 +134,12 @@
>
{{ $t('quests') }}
+
+ {{ $t('customizations') }}
+
{{ $t('editAvatar') }}
- {{ $t('backgrounds') }}
diff --git a/website/client/src/components/inventory/items/index.vue b/website/client/src/components/inventory/items/index.vue
index 5c278b176a..b64658a276 100644
--- a/website/client/src/components/inventory/items/index.vue
+++ b/website/client/src/components/inventory/items/index.vue
@@ -444,7 +444,7 @@ export default {
const isSearched = !searchText || item.text()
.toLowerCase()
.indexOf(searchText) !== -1;
- if (isSearched) {
+ if (isSearched && item) {
itemsArray.push({
...item,
class: `${group.classPrefix}${item.key}`,
diff --git a/website/client/src/components/inventory/stable/index.vue b/website/client/src/components/inventory/stable/index.vue
index 13c7481bb4..8b2bacd241 100644
--- a/website/client/src/components/inventory/stable/index.vue
+++ b/website/client/src/components/inventory/stable/index.vue
@@ -134,56 +134,57 @@
v-for="(petGroup) in petGroups"
v-if="!anyFilterSelected || viewOptions[petGroup.key].selected"
:key="petGroup.key"
+ :class="{ hide: viewOptions[petGroup.key].animalCount === 0 }"
>
{{ petGroup.label }}
-
{{ $t('mounts') }}
@@ -196,52 +197,55 @@
v-for="mountGroup in mountGroups"
v-if="!anyFilterSelected || viewOptions[mountGroup.key].selected"
:key="mountGroup.key"
+ :class="{ hide: viewOptions[mountGroup.key].animalCount === 0 }"
>
{{ mountGroup.label }}
-
-
+
-
+
-
- {{ item.name }}
-
-
-
-
-
+
+ {{ item.name }}
+
+
+
+
+
+
+
-
@@ -330,6 +329,14 @@
display: inline-block;
}
+ .pet-row {
+ flex-wrap: wrap;
+
+ .pet-group:not(:last-of-type) {
+ margin-right: 24px;
+ }
+ }
+
.GreyedOut {
opacity: 0.3;
}
@@ -343,24 +350,11 @@
}
.stable {
-
- .standard-page {
- padding-right:0;
- }
-
- .standard-page .clearfix .float-right {
- margin-right: 24px;
- }
-
.svg-icon.inline.icon-16 {
vertical-align: bottom;
}
}
- .last {
- margin-right: 0 !important;
- }
-
.no-focus:focus {
background-color: inherit;
color: inherit;
diff --git a/website/client/src/components/shops/balanceInfo.vue b/website/client/src/components/shops/balanceInfo.vue
index f180d7292b..d473034538 100644
--- a/website/client/src/components/shops/balanceInfo.vue
+++ b/website/client/src/components/shops/balanceInfo.vue
@@ -1,47 +1,41 @@
-
-
+
-
{{ currency.value | roundBigNumber }}
-
-
+
+
diff --git a/website/client/src/components/shops/featuredItemsHeader.vue b/website/client/src/components/shops/featuredItemsHeader.vue
index 416814bec0..d0a89b16e4 100644
--- a/website/client/src/components/shops/featuredItemsHeader.vue
+++ b/website/client/src/components/shops/featuredItemsHeader.vue
@@ -89,7 +89,7 @@ export default {
-
diff --git a/website/client/src/components/ui/selectList.vue b/website/client/src/components/ui/selectList.vue
index c661f72a44..8d9c947e46 100644
--- a/website/client/src/components/ui/selectList.vue
+++ b/website/client/src/components/ui/selectList.vue
@@ -104,6 +104,10 @@ export default {
activeKeyProp: {
type: String,
},
+ directSelect: {
+ type: Boolean,
+ default: false,
+ },
disabledProp: {
type: String,
},
@@ -132,13 +136,17 @@ export default {
},
methods: {
getKeyProp (item) {
- return this.keyProp ? item[this.keyProp] : item;
+ return this.keyProp ? item[this.keyProp] : item.key || item.identifier;
},
isDisabled (item) {
return typeof item[this.disabledProp] === 'undefined' ? false : item[this.disabledProp];
},
selectItem (item) {
- this.selected = this.getKeyProp(item);
+ if (this.directSelect) {
+ this.selected = item;
+ } else {
+ this.selected = this.getKeyProp(item);
+ }
this.$emit('select', item);
},
isSelected (item) {
diff --git a/website/client/src/components/ui/showMoreButton.vue b/website/client/src/components/ui/showMoreButton.vue
index fb0fdd8496..7e24ad75c0 100644
--- a/website/client/src/components/ui/showMoreButton.vue
+++ b/website/client/src/components/ui/showMoreButton.vue
@@ -1,6 +1,6 @@