Compare commits

..

417 Commits

Author SHA1 Message Date
Sabe Jones dd334f487e 5.28.1 2024-09-03 16:27:04 -05:00
Weblate cd5c86fb69 Translated using Weblate (Hungarian)
Currently translated at 85.6% (334 of 390 strings)

Translated using Weblate (German)

Currently translated at 91.5% (2923 of 3193 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 2.6% (5 of 188 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 86.9% (766 of 881 strings)

Translated using Weblate (German)

Currently translated at 91.4% (2919 of 3193 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (German)

Currently translated at 91.3% (2917 of 3193 strings)

Translated using Weblate (German)

Currently translated at 91.3% (2917 of 3193 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (German)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (German)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 100.0% (387 of 387 strings)

Translated using Weblate (German)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.6% (869 of 881 strings)

Translated using Weblate (Russian)

Currently translated at 29.7% (56 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 98.9% (386 of 390 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (German)

Currently translated at 91.1% (2912 of 3193 strings)

Co-authored-by: Andrey <andrey.martinich@gmail.com>
Co-authored-by: Angela Yulenis Ramos Carreño <yulieeniss@gmail.com>
Co-authored-by: DaniilBerkut <danilen2472@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Kacchan <h.mrena97@gmail.com>
Co-authored-by: Noah März <maerznoah@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Npc
Translation: Habitica/Settings
2024-09-03 20:57:16 +02:00
Phillip Thelen 7878761b6f Fix debug footer layout (#15308) 2024-08-30 08:29:56 -05:00
Phillip Thelen d3b63abdd3 🧑‍💼🎛️ Overhaul (#15270)
* Add option to search for users by email or username in admin panel

* Make Admin panel design more consistent

* fix test

* fix width of items

* escape regex for searching users

* load own user when pressing enter on empty field

* add styling for warning buttons

* improve sub styling

* fix checkbox alignment in admin panel

* Unify date preview display

* Fix bottom button display

* admin panel display improvements

* remove autocannon file

* search improvements

* time travel button display fix

* fix loading spinner

* fix sorting

* Split email search into multiple queries

* fix email search

* remove console

* fix line break
2024-08-29 09:15:45 -05:00
Weblate 23fad37205 Merge branch 'origin/develop' into Weblate. 2024-08-29 15:43:45 +02:00
Sabe Jones 88558e6b98 fix(rebirth): add missing await #15300 @CuriousMagpie 2024-08-29 08:42:26 -05:00
Sabe Jones a84ee8497b 5.28.0 2024-08-29 08:39:46 -05:00
Sabe Jones d560ee2da1 chore(subproject): update habitica-images 2024-08-29 08:39:41 -05:00
Weblate fd3fce110e Translated using Weblate (Hungarian)
Currently translated at 90.9% (121 of 133 strings)

Translated using Weblate (German)

Currently translated at 99.2% (266 of 268 strings)

Translated using Weblate (German)

Currently translated at 91.1% (2910 of 3193 strings)

Translated using Weblate (Hungarian)

Currently translated at 77.4% (103 of 133 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 91.8% (238 of 259 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Polish)

Currently translated at 65.1% (172 of 264 strings)

Translated using Weblate (Polish)

Currently translated at 55.2% (1765 of 3193 strings)

Translated using Weblate (Polish)

Currently translated at 99.4% (181 of 182 strings)

Translated using Weblate (German)

Currently translated at 98.7% (233 of 236 strings)

Translated using Weblate (German)

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (3085 of 3153 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (235 of 235 strings)

Translated using Weblate (Japanese)

Currently translated at 96.0% (763 of 794 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (874 of 878 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (390 of 390 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (184 of 188 strings)

Translated using Weblate (German)

Currently translated at 99.4% (187 of 188 strings)

Translated using Weblate (Turkish)

Currently translated at 94.7% (108 of 114 strings)

Translated using Weblate (Turkish)

Currently translated at 69.0% (295 of 427 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Turkish)

Currently translated at 92.8% (155 of 167 strings)

Translated using Weblate (German)

Currently translated at 98.9% (90 of 91 strings)

Translated using Weblate (German)

Currently translated at 96.7% (88 of 91 strings)

Translated using Weblate (German)

Currently translated at 94.5% (86 of 91 strings)

Translated using Weblate (German)

Currently translated at 82.4% (75 of 91 strings)

Translated using Weblate (German)

Currently translated at 76.9% (70 of 91 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (German)

Currently translated at 69.2% (63 of 91 strings)

Translated using Weblate (German)

Currently translated at 92.2% (2908 of 3153 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 92.3% (168 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 92.1% (2906 of 3153 strings)

Translated using Weblate (Hungarian)

Currently translated at 81.4% (136 of 167 strings)

Co-authored-by: Demir <feoxay32@gmail.com>
Co-authored-by: Dorota <dorakonopna3102@gmail.com>
Co-authored-by: Hanafi <naflizo@gmail.com>
Co-authored-by: Kacchan <h.mrena97@gmail.com>
Co-authored-by: Noah März <maerznoah@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/front/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/id/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2024-08-29 11:53:26 +02:00
Natalie 1bce2b0e28 Orb of Rebirth fix (#15300)
* add await to buyModal.vue, small cosmetic improvement

* attempt to fix t.response

* removing attempt to fix t.response

* add break to asyncResource.js to prevent t.response error

* revert asyncResource.js change
2024-08-28 16:11:01 -05:00
Sabe Jones 06a59bfe03 fix(tasks): always prune __v and add id (#15301)
Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-28 15:05:34 -05:00
Sabe Jones 83a430afad fix broken test (#15304)
Co-authored-by: Phillip Thelen <phillip@habitica.com>
2024-08-28 14:39:52 -05:00
Natalie 949f638b6e 2024-09 Content Prebuild (#15295)
* 202409 subscriber gear

* 2024-09 enchanted armoire gear

* 2024 fall festival gear

* 2024-09 background

* 2024-09 pet quest

* typos and update featuredItems.js

* quest and typo fix

* fix subscriber set name

* text amendments

* update quest title
2024-08-23 12:47:25 -05:00
dependabot[bot] 2b2193e9ce Bump axios from 1.6.8 to 1.7.4 (#15299)
Bumps [axios](https://github.com/axios/axios) from 1.6.8 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.8...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-20 12:53:58 -04:00
Sabe Jones 0709bada87 5.27.4 2024-08-20 09:58:54 -05:00
Weblate 506586b74c Translated using Weblate (Russian)
Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (German)

Currently translated at 92.1% (2904 of 3153 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (876 of 878 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hungarian)

Currently translated at 79.0% (132 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 78.4% (131 of 167 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 71.2% (119 of 167 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Indonesian)

Currently translated at 90.7% (235 of 259 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 96.0% (763 of 794 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 95.9% (762 of 794 strings)

Translated using Weblate (German)

Currently translated at 92.0% (2902 of 3153 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (3153 of 3153 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 95.7% (760 of 794 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.5% (3108 of 3153 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (235 of 235 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3153 of 3153 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Russian)

Currently translated at 98.2% (863 of 878 strings)

Translated using Weblate (German)

Currently translated at 91.9% (2898 of 3153 strings)

Translated using Weblate (Russian)

Currently translated at 97.2% (854 of 878 strings)

Translated using Weblate (Indonesian)

Currently translated at 95.7% (225 of 235 strings)

Translated using Weblate (Indonesian)

Currently translated at 96.4% (110 of 114 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.2% (132 of 133 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.4% (187 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (794 of 794 strings)

Translated using Weblate (Indonesian)

Currently translated at 97.9% (189 of 193 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2894 of 3153 strings)

Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Gleb <imejloman@gmail.com>
Co-authored-by: Hanafi <naflizo@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kacchan <h.mrena97@gmail.com>
Co-authored-by: Koldo Almandoz Forcen <koldo.almandoz.forcen@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Static Meteor <josusantaeufemiaiglesias@gmail.com>
Co-authored-by: TORIKI <toriki.cn@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Vasily Maslyukov <vasily.maslyukov@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Widy Nuryadi <jamesxandrosneutron@gmail.com>
Co-authored-by: Юрий Артамонов <zilberstein2211@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/id/
Translate-URL: https://translate.habitica.com/projects/habitica/death/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/id/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/id/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/id/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/id/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/id/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/hu/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-08-20 16:52:12 +02:00
Phillip Thelen 99b2ee273f Fix issues with task scoring and in-app-reward retrieval (#15294)
* remove obsolete class and computed

* correctly load equipped gear

* load purchased for in app rewards
2024-08-15 10:38:20 -05:00
Phillip Thelen aa6e536851 always load users version field 2024-08-14 11:59:05 -05:00
Natalie 2a2c1af7ba Add rage button to debug menu (#15291)
* + Rage

* tinkering

* remove if statement wrapper and modify error message

* add test cases

* more work on test cases

* adding contexts to test cases

* test(debug): fix up tests

* fix(lint): whisepate

---------

Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-13 00:34:37 -05:00
Sabe Jones 48e381d702 fix(CI): force apt update before fetching libkrb5-dev 2024-08-12 17:36:01 -05:00
Phillip Thelen 9aafd76746 Improve the performance of some frequently used API calls (#15251)
* use lean for getting task lists

* Only load necessary user data for group-plans call

Also don’t make a db request for groups if the user is in none

* Only load necessary user fields for in app rewards

* Optimize updateStore by not checking every item

* Only load necessary user data for task scoring

* improve performance of inbox request calls

* merge fix

* fix scoring task call

* add quests to scoring call

* fix showing official pinned items

* also load achievements
2024-08-12 16:45:35 -05:00
Sabe Jones 0069af78a3 5.27.3 2024-08-12 15:29:07 -05:00
Weblate c25fe7eb3d Merge branch 'origin/develop' into Weblate. 2024-08-12 22:22:07 +02:00
Weblate b9a9013685 Translated using Weblate (French)
Currently translated at 99.7% (792 of 794 strings)

Translated using Weblate (French)

Currently translated at 100.0% (387 of 387 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2892 of 3153 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Turkish)

Currently translated at 92.2% (154 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 99.5% (425 of 427 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.2% (764 of 794 strings)

Translated using Weblate (Russian)

Currently translated at 94.1% (2969 of 3153 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3153 of 3153 strings)

Translated using Weblate (French)

Currently translated at 99.2% (788 of 794 strings)

Translated using Weblate (German)

Currently translated at 98.7% (232 of 235 strings)

Translated using Weblate (German)

Currently translated at 91.6% (2890 of 3153 strings)

Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Vasily Maslyukov <vasily.maslyukov@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: billy <kreideraine@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translation: Habitica/Achievements
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Groups
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
2024-08-12 21:35:26 +02:00
Phillip Thelen 54d075e4fd Correctly load bootstrap radio component (#15293) 2024-08-12 10:53:35 -05:00
Phillip Thelen 1c40044525 better timezone handling 2024-08-09 14:26:49 -05:00
Phillip Thelen 5784694dc9 fix loading user data in app.vue (#15292)
* fix handling user version

* fix notification display
2024-08-09 08:36:06 -05:00
Phillip Thelen 7af4a6ff11 load sounds from aws (#15249) 2024-08-06 15:04:16 -05:00
Phillip Thelen a601be0666 Don’t load browser-script again if it’s already the correct language (#15263) 2024-08-06 15:03:26 -05:00
Phillip Thelen 1be169a105 Reduce size of client js bundles (#15264)
* add packages

* Only include the needed parts of BootstrapVue

* remove yargs from client

* treeshake validator library

* formatting

* fix import
2024-08-06 12:53:44 -05:00
Phillip Thelen 6b02af69f2 Refactor the root App to load less data for front page visits (#15265)
* refactor root app to not load everything when visiting landing page

# Conflicts:
#	website/client/src/app.vue

* fix lint

* fix hiding loading screen

* fix showing snackbars when not logged in

* remove console
2024-08-06 12:49:14 -05:00
Phillip Thelen 1fe4bd2de7 Improve rate limiting (#15272)
* Improve rate limiting

* make rate limiter config names more consistent

* fix tests and add new one

* correct math
2024-08-06 12:45:27 -05:00
Sabe Jones afd00a8ab6 Remove and clean up unused invite notif (#15279)
* fix(notifications): remove and clean up unused invite notif

* fix(lint): remove unused const

* refactor(invites): updateMany in migration, don't load inviter unless needed

* fix(lint): remove extra whitespace

* fix(groups): remove more broken inviter logic

---------

Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-06 12:43:24 -05:00
Phillip Thelen 63918b3c20 Fix schedule using wrong month at the beginning hours of month (#15290)
* Fix schedule using wrong month at the beginning hours of month

* fix broken test

* fix switchover for time based matchers

* Fix scheduling issue related to timezones

* Fix end date creating issues
2024-08-06 12:35:05 -05:00
Sabe Jones 6293a4b936 5.27.2 2024-08-06 12:05:46 -05:00
Weblate 44502092ad Translated using Weblate (French)
Currently translated at 97.8% (184 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3153 of 3153 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 96.2% (181 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 88.8% (167 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (235 of 235 strings)

Translated using Weblate (French)

Currently translated at 99.8% (3148 of 3153 strings)

Translated using Weblate (Indonesian)

Currently translated at 87.7% (165 of 188 strings)

Translated using Weblate (French)

Currently translated at 99.1% (787 of 794 strings)

Translated using Weblate (German)

Currently translated at 95.3% (757 of 794 strings)

Translated using Weblate (German)

Currently translated at 99.4% (385 of 387 strings)

Translated using Weblate (German)

Currently translated at 100.0% (878 of 878 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Russian)

Currently translated at 96.0% (763 of 794 strings)

Translated using Weblate (Portuguese)

Currently translated at 35.1% (66 of 188 strings)

Translated using Weblate (Portuguese)

Currently translated at 25.5% (48 of 188 strings)

Co-authored-by: Catarina Rocha <caticalhau312@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Hanafi <naflizo@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translation: Habitica/Backgrounds
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
2024-08-06 19:04:21 +02:00
Natalie ce0e8284fe Update static navbar on mobile (#15289)
* update logo on static pages (mobile)

* remove typo
2024-08-02 09:43:32 -05:00
Phillip Thelen 15f104ddd0 Add a timeout to mongoldb connections (#15258)
* Add option to set a socket timeout for mongodb requests

* Handle mongodb timeouts better

* add default to config
2024-08-01 10:28:54 -05:00
Natalie 7f6ae8ffbf Navigation Bar Visual Improvements (#15278)
* update privacy policy

* Fix serving memoized content

* add missing info to mystery item strings

* add missing string info to July mystery items

* fix food

* melior updates - loading screen & menu bar

* updates to currency tray spacing

* table styling

* one last table style

* add margin adjustment

* fix spacing

* clean up spacing styles

* remove table rounded corners

* correct faq tyops

* spacing updates

* fix web challenge instructions

* beach umbrella you are not shady except when you are

* fix mobile margin weirdness

* update wording on answers

* mobile spacing

* add chart showing details of item 1.4

* revert unrelated changes

* melior centering and icon spacing

* remove unnecessary files

* rejiggering spacing

* remove errant comma

* remove unnecessary files

* remove duplicated class

* remove duplicated class

* ...and more spacing

* notification bubble

* notification icon & currency alignment

* fix(CSS): clean up some unnecessary classes

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-01 10:06:21 -05:00
Phillip Thelen b2ecfb5a32 fix date dependatent time travel test (#15285) 2024-08-01 09:16:58 -05:00
Natalie fa6ba8b668 update armorArmoireBasketballUniformNotes to correct stat display (#15262)
Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-01 09:13:18 -05:00
Natalie 826dffc794 FAQ Typos (#15282)
* update privacy policy

* Fix serving memoized content

* add missing info to mystery item strings

* add missing string info to July mystery items

* fix food

* melior updates - loading screen & menu bar

* updates to currency tray spacing

* table styling

* one last table style

* add margin adjustment

* fix spacing

* clean up spacing styles

* remove table rounded corners

* correct faq tyops

* spacing updates

* fix web challenge instructions

* update wording on answers

* revert unrelated changes

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-08-01 09:11:49 -05:00
Sabe Jones 688190ac4a 5.27.1 2024-08-01 08:37:23 -05:00
Sabe Jones 4909a3b537 Merge branch 'develop' into release 2024-08-01 08:37:14 -05:00
Weblate 64e2150f44 Translated using Weblate (Portuguese)
Currently translated at 24.4% (46 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (3085 of 3153 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.6% (109 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.4% (3105 of 3153 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 79.2% (149 of 188 strings)

Translated using Weblate (Portuguese)

Currently translated at 23.4% (44 of 188 strings)

Translated using Weblate (Portuguese)

Currently translated at 23.4% (44 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 94.7% (752 of 794 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (387 of 387 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese)

Currently translated at 95.3% (184 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (878 of 878 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (878 of 878 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.2% (764 of 794 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Indonesian)

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Co-authored-by: Catarina Rocha <caticalhau312@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Hanafi <naflizo@gmail.com>
Co-authored-by: Muhammad Hanafi <Cytricgame@gmail.com>
Co-authored-by: Márcio Ramos Corrêa <marcio.ramos.correa@gmail.com>
Co-authored-by: Pierre Huang <3541262043@qq.com>
Co-authored-by: Static Meteor <josusantaeufemiaiglesias@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/uk/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Tasks
2024-08-01 15:36:26 +02:00
Sabe Jones 3f7abc459c fix(lint): clean up migration for ongoing use 2024-07-30 15:43:29 -05:00
Sabe Jones 618cdafd10 fix(lint): clean up migration for ongoing use 2024-07-30 15:42:54 -05:00
Sabe Jones e7b37d0378 chore(migration): set up for Naming Day 2024 2024-07-30 15:13:33 -05:00
Sabe Jones 3f3e2525d2 chore(migration): set up for Naming Day 2024 2024-07-30 15:12:56 -05:00
Phillip Thelen 765e08f999 fix displaying if a pet is hatchable (#15281) 2024-07-30 12:16:43 -05:00
dependabot[bot] 598bc29647 Bump fast-xml-parser from 4.3.4 to 4.4.1 (#15280)
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 4.3.4 to 4.4.1.
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v4.3.4...v4.4.1)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-07-29 17:24:11 -05:00
Sabe Jones 39ccddfb1c fix(customization): restore broken popovers 2024-07-29 16:17:33 -05:00
Sabe Jones 108214a217 fix(lint): remove console statement 2024-07-29 14:15:47 -05:00
Sabe Jones 271f40e355 Squashed commit of the following:
commit 8ed95731cb
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jul 23 17:59:56 2024 +0200

    fix hatched dialog

commit 53242ad96c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jul 23 17:38:13 2024 +0200

    fix popover not showing

commit ce4bfd25bd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jul 3 17:28:30 2024 +0200

    move item popover to own component

commit 2e6a300c46
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 1 18:48:24 2024 +0200

    make scaled sprites look nice

commit a3cbadb8c2
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 1 18:48:17 2024 +0200

    fix hatching dialog

commit 0e5126df5e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 1 18:48:12 2024 +0200

    fix popover alignment

commit 7362af9236
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 28 17:07:03 2024 +0200

    fix item display

commit cf353efdb7
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 28 16:59:13 2024 +0200

    fix pet display

commit caf0cba9f2
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 28 15:24:39 2024 +0200

    fix background icon display

commit 3b06febc01
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 26 12:30:04 2024 +0200

    fix sprites for notifications

commit 160b2debdc
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 26 10:35:13 2024 +0200

    fix gear display in profile

commit b200a2f17d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 26 10:28:11 2024 +0200

    fix sprites for keys to the kennel

commit 3614e7a8fb
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 26 10:28:00 2024 +0200

    fix sprites on avatar customization

commit 35f993d055
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 26 09:18:41 2024 +0200

    fix hover icons

commit 28fc80115e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jun 11 16:50:37 2024 +0200

    remove console

commit b041c67679
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jun 11 15:18:44 2024 +0200

    more lint fixes

commit f4261d0440
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jun 11 15:18:16 2024 +0200

    fix lint

commit 878ee8f77b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jun 11 13:23:08 2024 +0200

    support gifs

commit aac24715aa
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jun 11 13:15:52 2024 +0200

    move avatar customization to sprites

commit f4d3663130
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 7 17:25:19 2024 +0200

    Move more sprites out of css

commit 6e6b4c981a
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 7 16:59:30 2024 +0200

    add new sprite to item and shopItem component

commit 8712413f5d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 7 16:37:24 2024 +0200

    use new sprites for pets list

commit 1172893826
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 5 09:43:51 2024 +0200

    Begin building new system for loading sprites
2024-07-29 14:10:27 -05:00
Phillip Thelen e801547580 Seasonal gear fix (#15255)
* cleanup unneeded season definitions

* assign first winter seasonal gear right season

* add missing winter definition

* Fix enddate for winter galas

* fix lint

* fix halloween sprites

* set season

* fix loading habitoween sprites

* add missing customization shop sprites

* Fix test

* update customization shop sprites
2024-07-29 10:30:23 -05:00
Phillip Thelen fc11941186 Steampunk gear should not have an end date (#15256) 2024-07-29 10:29:42 -05:00
Sabe Jones 882fad3113 5.27.0 2024-07-29 10:04:59 -05:00
Weblate 6168492711 Translated using Weblate (French)
Currently translated at 99.7% (386 of 387 strings)

Translated using Weblate (French)

Currently translated at 100.0% (878 of 878 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (235 of 235 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3153 of 3153 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (794 of 794 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (387 of 387 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (876 of 878 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.9% (129 of 133 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.5% (3067 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 78.1% (147 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.2% (764 of 794 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.7% (108 of 114 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (261 of 264 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.2% (3055 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 39.3% (74 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.2% (764 of 794 strings)

Translated using Weblate (Spanish)

Currently translated at 99.3% (789 of 794 strings)

Translated using Weblate (Indonesian)

Currently translated at 79.1% (72 of 91 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.6% (163 of 167 strings)

Translated using Weblate (Japanese)

Currently translated at 96.8% (760 of 785 strings)

Co-authored-by: Alex Kalergi <nitrogenphospat@proton.me>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Muhammad Hanafi <Cytricgame@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/id/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/uk/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-07-29 17:03:35 +02:00
Natalie 2aade9aaa6 August 2024 Content Prebuild (#15277)
* add sprites

* 2024-08 content build

* fixing stuff

* update boss rage effects

* update quest rage text and releaseDates.js

* update shop-featuredItems.js

* fricken fracken beach umbrella

* more beach umbrella

* remove egregious typo
2024-07-25 12:55:38 -05:00
Sabe Jones 4789946c4e 5.26.3 2024-07-25 08:53:26 -05:00
Sabe Jones a69d8877c9 Merge branch 'develop' into release 2024-07-25 08:51:26 -05:00
Weblate 7cc0c3bc57 Translated using Weblate (German)
Currently translated at 91.8% (2888 of 3143 strings)

Translated using Weblate (Slovak)

Currently translated at 97.2% (177 of 182 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (785 of 785 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Slovak)

Currently translated at 92.3% (12 of 13 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2883 of 3143 strings)

Translated using Weblate (Slovak)

Currently translated at 96.7% (176 of 182 strings)

Translated using Weblate (Indonesian)

Currently translated at 33.5% (63 of 188 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 2.6% (5 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (765 of 785 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Indonesian)

Currently translated at 32.9% (62 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 29.2% (55 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (German)

Currently translated at 91.6% (2881 of 3143 strings)

Translated using Weblate (Slovak)

Currently translated at 93.9% (171 of 182 strings)

Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Turkish)

Currently translated at 80.4% (107 of 133 strings)

Translated using Weblate (Turkish)

Currently translated at 7.9% (15 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 95.6% (751 of 785 strings)

Translated using Weblate (French)

Currently translated at 98.3% (772 of 785 strings)

Translated using Weblate (German)

Currently translated at 91.6% (2879 of 3143 strings)

Translated using Weblate (French)

Currently translated at 98.0% (770 of 785 strings)

Co-authored-by: Adithya Mahadev. B <adithyamahadev2521@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Ece Ekinci <plutonium3613@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Hanafi <naflizo@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/uk/
Translation: Habitica/Achievements
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Npc
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Spells
Translation: Habitica/Tasks
2024-07-25 15:49:36 +02:00
Sabe Jones fb78495a1b feat(avatar): popovers for customization 2024-07-24 16:38:58 -05:00
Phillip Thelen 22def5111f fix purchasing wacky potions (#15276) 2024-07-24 10:35:26 -05:00
Phillip Thelen 6e91d51def Fix some scheduling issues (#15274)
* fix logic for time travelers schedule

* fix potion availability

* fix tests
2024-07-23 11:33:59 -05:00
Sabe Jones f3b8a4e931 5.26.2 2024-07-21 13:05:07 -05:00
Weblate 69afa52beb Merge branch 'origin/develop' into Weblate. 2024-07-21 20:04:10 +02:00
Weblate f09a39d27c Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (872 of 875 strings)

Translated using Weblate (German)

Currently translated at 91.5% (2877 of 3143 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Spanish)

Currently translated at 98.2% (771 of 785 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.9% (3046 of 3143 strings)

Translated using Weblate (Portuguese)

Currently translated at 57.0% (1793 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (765 of 785 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Italian)

Currently translated at 87.5% (7 of 8 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Italian)

Currently translated at 86.1% (368 of 427 strings)

Translated using Weblate (Italian)

Currently translated at 11.1% (21 of 188 strings)

Translated using Weblate (Italian)

Currently translated at 94.2% (740 of 785 strings)

Translated using Weblate (Italian)

Currently translated at 54.9% (50 of 91 strings)

Translated using Weblate (Italian)

Currently translated at 95.7% (45 of 47 strings)

Translated using Weblate (Italian)

Currently translated at 97.4% (188 of 193 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Italian)

Currently translated at 74.9% (194 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (German)

Currently translated at 91.4% (2875 of 3143 strings)

Translated using Weblate (Italian)

Currently translated at 94.6% (828 of 875 strings)

Translated using Weblate (Italian)

Currently translated at 94.0% (823 of 875 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (German)

Currently translated at 91.4% (2873 of 3143 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 85.7% (114 of 133 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (German)

Currently translated at 91.3% (2871 of 3143 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1 of 1 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.4% (21 of 22 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (German)

Currently translated at 91.2% (2869 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (French)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (German)

Currently translated at 98.4% (131 of 133 strings)

Translated using Weblate (French)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (French)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3143 of 3143 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.5% (239 of 240 strings)

Translated using Weblate (French)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (German)

Currently translated at 98.7% (237 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Slovak)

Currently translated at 97.9% (189 of 193 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hebrew)

Currently translated at 92.8% (155 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (781 of 785 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Portuguese)

Currently translated at 65.3% (153 of 234 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (261 of 264 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.9% (3046 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.5% (144 of 188 strings)

Translated using Weblate (Indonesian)

Currently translated at 28.1% (53 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (781 of 785 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.1% (377 of 384 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.6% (163 of 167 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (258 of 259 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (German)

Currently translated at 91.2% (2867 of 3143 strings)

Translated using Weblate (Ukrainian)

Currently translated at 98.4% (773 of 785 strings)

Translated using Weblate (French)

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (French)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Ukrainian)

Currently translated at 61.7% (1940 of 3143 strings)

Translated using Weblate (Portuguese)

Currently translated at 57.0% (1793 of 3143 strings)

Translated using Weblate (German)

Currently translated at 91.1% (2865 of 3143 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (3085 of 3102 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 61.7% (1940 of 3143 strings)

Translated using Weblate (German)

Currently translated at 91.0% (2863 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Slovak)

Currently translated at 74.9% (588 of 785 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 95.6% (109 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (German)

Currently translated at 91.0% (2861 of 3143 strings)

Translated using Weblate (German)

Currently translated at 98.7% (231 of 234 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 99.2% (262 of 264 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3143 of 3143 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (German)

Currently translated at 90.9% (2857 of 3143 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.4% (382 of 384 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.4% (382 of 384 strings)

Translated using Weblate (Slovak)

Currently translated at 97.6% (375 of 384 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (German)

Currently translated at 99.7% (383 of 384 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.8% (3043 of 3143 strings)

Translated using Weblate (German)

Currently translated at 90.8% (2855 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.7% (3041 of 3143 strings)

Translated using Weblate (French)

Currently translated at 99.6% (3132 of 3143 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (French)

Currently translated at 100.0% (384 of 384 strings)

Translated using Weblate (Indonesian)

Currently translated at 91.3% (799 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 98.9% (380 of 384 strings)

Translated using Weblate (Indonesian)

Currently translated at 95.3% (184 of 193 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (377 of 378 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.0% (157 of 167 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (192 of 193 strings)

Translated using Weblate (French)

Currently translated at 99.0% (3114 of 3143 strings)

Translated using Weblate (German)

Currently translated at 90.7% (2853 of 3143 strings)

Translated using Weblate (French)

Currently translated at 100.0% (785 of 785 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.0% (157 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 98.5% (421 of 427 strings)

Translated using Weblate (French)

Currently translated at 98.7% (3105 of 3143 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (German)

Currently translated at 98.4% (190 of 193 strings)

Translated using Weblate (French)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Turkish)

Currently translated at 5.8% (11 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (785 of 785 strings)

Translated using Weblate (German)

Currently translated at 98.4% (773 of 785 strings)

Translated using Weblate (German)

Currently translated at 65.9% (60 of 91 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (873 of 875 strings)

Translated using Weblate (German)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3143 of 3143 strings)

Co-authored-by: Alex G <powerali18@gmail.com>
Co-authored-by: Antonio Spinelli <tonicospinelli@users.noreply.translate.habitica.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Elisa Bailo <bailoelisa5@gmail.com>
Co-authored-by: Eva <kornmann.eva@gmx.de>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Isabela de Carvalho <isabela.c.escritora@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Justcallme rye <Blizzardscf32@gmail.com>
Co-authored-by: Miguel Rocha de Macedo Amaral <miguelrmamaral@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Paolo Bizzarri <pibizza@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Vinicius Rodrigues <suburbanizar@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yan <ariamao0802@gmail.com>
Co-authored-by: billypat <kreideraine@gmail.com>
Co-authored-by: diamondmonster09 <diamondmonster09@gmail.com>
Co-authored-by: tamara <tab.bravani@gmail.com>
Co-authored-by: 过客是个铁憨憨 <1811304592@qq.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/he/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/id/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
Translate-URL: https://translate.habitica.com/projects/habitica/character/es/
Translate-URL: https://translate.habitica.com/projects/habitica/character/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/id/
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/de/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/it/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/id/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/es/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/it/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/merch/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/it/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/it/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/tr/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Merch
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-07-21 20:03:59 +02:00
Phillip Thelen 2e71963fbf Update schedule.js (#15273)
Add SandSculpture for July
2024-07-21 12:58:28 -05:00
Sabe Jones f740a92fb7 Update README.md
fix awkward line breaks
2024-07-19 17:37:31 -05:00
Sabe Jones 21e7ddea16 Update README.md 2024-07-19 17:36:26 -05:00
Sabe Jones 923d90cf22 Merge branch 'sabrecat/postfixes' into develop 2024-07-17 17:43:05 -05:00
Sabe Jones b386a1917d Merge branch 'sabrecat/more-api-sunset' into develop 2024-07-17 17:42:28 -05:00
Sabe Jones 4a5427b2b2 Merge branch 'develop' into release 2024-07-03 14:23:53 -05:00
Sabe Jones 04554c5309 5.26.1 2024-07-03 14:23:37 -05:00
Phillip Thelen 5ef88b5c56 Update releaseDates.js 2024-07-03 18:52:23 +02:00
Sabe Jones 1f2397b81a fix(quests): adjust margins 2024-07-02 17:13:40 -05:00
Weblate 892c4934d5 Translated using Weblate (Japanese)
Currently translated at 99.2% (3080 of 3103 strings)

Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translation: Habitica/Gear
2024-07-01 22:51:54 +02:00
Sabe Jones 60d5aaaaa6 fix(memoize): enumerate food 2024-07-01 09:02:12 -05:00
Sabe Jones f506b840ed fix(lint): add missing whitespace for curlies 2024-06-28 23:34:32 -05:00
Sabe Jones f357750d88 test(chat): coverage for new sunsets 2024-06-28 23:29:58 -05:00
Sabe Jones a2dbe68338 Merge branch 'release' into sabrecat/more-api-sunset 2024-06-28 22:20:07 -05:00
Sabe Jones 491d2cfab1 fix(modals): various tweks 2024-06-28 22:03:45 -05:00
Sabe Jones fa91abb739 Merge branch 'develop' into sabrecat/postfixes 2024-06-28 21:28:39 -05:00
Sabe Jones 6b46d04537 Merge branch 'develop' into release 2024-06-28 11:27:58 -05:00
Sabe Jones b90457c04f fix(test): correct hatching potion test 2024-06-28 11:20:21 -05:00
Sabe Jones 379d98a91e fix(test): correct schedule test 2024-06-28 11:09:09 -05:00
Sabe Jones 07352480cd Merge remote-tracking branch 'origin/phillip/memoize-me' into develop 2024-06-28 10:26:38 -05:00
Phillip Thelen a6ff8e095a fix naming 2024-06-28 10:21:03 -05:00
Phillip Thelen 1fb44bbe73 fix naming 2024-06-28 10:20:11 -05:00
Phillip Thelen 5323849f90 fix naming 2024-06-28 17:16:45 +02:00
Sabe Jones 034327f647 5.26.0 2024-06-28 09:55:53 -05:00
Sabe Jones de9aac0988 Squashed commit of the following:
commit 8309686922
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 25 15:41:41 2024 -0400

    melior updates - loading screen & menu bar

commit 53608dd688
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 24 22:12:09 2024 +0200

    fix food

commit eecae86454
Merge: 95c562fdbc 960e262f19
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 16:05:04 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 960e262f19
Merge: c5bbadaacd 7ec8b84b01
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 16:03:40 2024 -0400

    Merge branch '2024-07-content-prebuild' of https://github.com/HabitRPG/habitica-private into 2024-07-content-prebuild

commit c5bbadaacd
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 16:03:23 2024 -0400

    add missing string info to July mystery items

commit 7ec8b84b01
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 11:29:17 2024 -0400

    add missing info to mystery item strings

commit 95c562fdbc
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 21 11:12:18 2024 +0200

    Fix serving memoized content

commit 877fe48225
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 12:23:24 2024 +0200

    correctly memoize conent api

commit e0f6f79c5b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 10:11:27 2024 +0200

    don’t build multiple times on heroku

commit f62254d68e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:40:20 2024 +0200

    fix client command

commit d054e6fc16
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:36:57 2024 +0200

    correct build call

commit 7231f699c1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:32:32 2024 +0200

    try setting up with heroku buildpack

commit 1dae0793fd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:50:32 2024 +0200

    call gulp build:prod

commit f18fbe86b6
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:40:53 2024 +0200

    build client

commit 61a61724ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:33:18 2024 +0200

    testing

commit 93cf30eb18
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:20:25 2024 +0200

    integration fix

commit cff08adcd0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:13:20 2024 +0200

    specify dev docker file

commit 4da2ed4a1f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:10:07 2024 +0200

    initialize stub

commit 11c5b26c59
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:08:45 2024 +0200

    test heroku file

commit ac85bb2e2d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:03:15 2024 +0200

    fix stub reference

commit 74dfb2710f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:01:27 2024 +0200

    test fixes

commit 8dbd3c3db1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:37:04 2024 +0200

    fix canOwn test

commit 74b3b348ff
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:32:31 2024 +0200

    fix buy test

commit 3386d61fde
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:30:37 2024 +0200

    fix debug tests

commit 19da14531c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:25 2024 +0200

    add chameleon to featured quests

commit 254dd80f24
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:14 2024 +0200

    fix import

commit 0bc3f16b4b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:22 2024 +0200

    add new content to new release file

commit 5184973bd5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:11 2024 +0200

    fix release date tests

commit b6accca5ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:06 2024 +0200

    fix armoire tests

commit fec68e6211
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:02:03 2024 +0200

    fix tests

commit fc63c906dd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:44:21 2024 +0200

    Improve test coverage

commit 3333f8f0f5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:24:59 2024 +0200

    allow hatching potions to have a release date

commit 89a3ac3dde
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:11:38 2024 +0200

    allow eggs to have a release date

    # Conflicts:
    #	test/content/armoire.test.js

commit 16551ec83f
Merge: f5f4974a73 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:03:12 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:02:47 2024 -0400

    update habitica images

commit f5f4974a73
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 14:58:13 2024 -0400

    update habitica-images

commit 162e337d14
Merge: f2506c3231 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:46:03 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:45:09 2024 -0400

    update sprites

commit f2506c3231
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:24:21 2024 -0400

    updated sprites css

commit d47641e25a
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 12:46:59 2024 -0400

    typo fix

commit fb8479ad1e
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 17 13:44:36 2024 -0400

    finish July prebuild

commit 3810cf3ef3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Jun 14 10:42:47 2024 -0400

    add chameleon quest

commit d05da3722c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 17:12:43 2024 -0400

    add June background notes

commit b8a3440ef2
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 16:40:04 2024 -0400

    fix mystery item and background description

commit 44d63032d8
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 15:38:23 2024 -0400

    add subscriber gear, enchanted armoire, and background

commit 9d7da91ec6
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 14:44:59 2024 -0400

    add sprites
2024-06-28 09:55:41 -05:00
Sabe Jones f55d836398 Squashed commit of the following:
commit 960e262f19
Merge: c5bbadaacd 7ec8b84b01
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 16:03:40 2024 -0400

    Merge branch '2024-07-content-prebuild' of https://github.com/HabitRPG/habitica-private into 2024-07-content-prebuild

commit c5bbadaacd
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 16:03:23 2024 -0400

    add missing string info to July mystery items

commit 7ec8b84b01
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 24 11:29:17 2024 -0400

    add missing info to mystery item strings

commit 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:02:47 2024 -0400

    update habitica images

commit 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:45:09 2024 -0400

    update sprites

commit d47641e25a
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 12:46:59 2024 -0400

    typo fix

commit fb8479ad1e
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 17 13:44:36 2024 -0400

    finish July prebuild

commit 3810cf3ef3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Jun 14 10:42:47 2024 -0400

    add chameleon quest

commit d05da3722c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 17:12:43 2024 -0400

    add June background notes

commit b8a3440ef2
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 16:40:04 2024 -0400

    fix mystery item and background description

commit 44d63032d8
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 15:38:23 2024 -0400

    add subscriber gear, enchanted armoire, and background

commit 9d7da91ec6
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 14:44:59 2024 -0400

    add sprites
2024-06-28 09:50:28 -05:00
Sabe Jones 287014518d Squashed commit of the following:
commit 28193f86fb
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 21 11:12:18 2024 +0200

    Fix serving memoized content

commit 877fe48225
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 12:23:24 2024 +0200

    correctly memoize conent api

commit e0f6f79c5b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 10:11:27 2024 +0200

    don’t build multiple times on heroku

commit f62254d68e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:40:20 2024 +0200

    fix client command

commit d054e6fc16
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:36:57 2024 +0200

    correct build call

commit 7231f699c1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:32:32 2024 +0200

    try setting up with heroku buildpack

commit 1dae0793fd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:50:32 2024 +0200

    call gulp build:prod

commit f18fbe86b6
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:40:53 2024 +0200

    build client

commit 61a61724ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:33:18 2024 +0200

    testing

commit 93cf30eb18
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:20:25 2024 +0200

    integration fix

commit cff08adcd0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:13:20 2024 +0200

    specify dev docker file

commit 4da2ed4a1f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:10:07 2024 +0200

    initialize stub

commit 11c5b26c59
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:08:45 2024 +0200

    test heroku file

commit ac85bb2e2d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:03:15 2024 +0200

    fix stub reference

commit 74dfb2710f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:01:27 2024 +0200

    test fixes

commit 8dbd3c3db1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:37:04 2024 +0200

    fix canOwn test

commit 74b3b348ff
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:32:31 2024 +0200

    fix buy test

commit 3386d61fde
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:30:37 2024 +0200

    fix debug tests

commit 19da14531c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:25 2024 +0200

    add chameleon to featured quests

commit 254dd80f24
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:14 2024 +0200

    fix import

commit 0bc3f16b4b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:22 2024 +0200

    add new content to new release file

commit 5184973bd5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:11 2024 +0200

    fix release date tests

commit b6accca5ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:06 2024 +0200

    fix armoire tests

commit fec68e6211
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:02:03 2024 +0200

    fix tests

commit fc63c906dd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:44:21 2024 +0200

    Improve test coverage

commit 3333f8f0f5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:24:59 2024 +0200

    allow hatching potions to have a release date

commit 89a3ac3dde
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:11:38 2024 +0200

    allow eggs to have a release date

    # Conflicts:
    #	test/content/armoire.test.js

commit 16551ec83f
Merge: f5f4974a73 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:03:12 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:02:47 2024 -0400

    update habitica images

commit f5f4974a73
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 14:58:13 2024 -0400

    update habitica-images

commit 162e337d14
Merge: f2506c3231 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:46:03 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:45:09 2024 -0400

    update sprites

commit f2506c3231
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:24:21 2024 -0400

    updated sprites css

commit d47641e25a
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 12:46:59 2024 -0400

    typo fix

commit fb8479ad1e
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 17 13:44:36 2024 -0400

    finish July prebuild

commit 3810cf3ef3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Jun 14 10:42:47 2024 -0400

    add chameleon quest

commit d05da3722c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 17:12:43 2024 -0400

    add June background notes

commit b8a3440ef2
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 16:40:04 2024 -0400

    fix mystery item and background description

commit 44d63032d8
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 15:38:23 2024 -0400

    add subscriber gear, enchanted armoire, and background

commit 9d7da91ec6
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 14:44:59 2024 -0400

    add sprites
2024-06-28 09:49:08 -05:00
Weblate b46e2da61b Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2024-06-28 16:41:20 +02:00
Emilia Skoglund ef47d6cf0b Translated using Weblate (Swedish)
Currently translated at 80.0% (619 of 773 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sv/
2024-06-28 05:28:46 +02:00
Daniel Faria Gomes 1f5d66cd58 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (773 of 773 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
2024-06-28 05:28:46 +02:00
Natalie Luhrs a88602a21f Translated using Weblate (Japanese)
Currently translated at 100.0% (773 of 773 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
2024-06-27 20:24:32 +02:00
Sabe Jones ffd2b4b76f fix(quests): modest Bundle revisions 2024-06-27 11:41:39 -05:00
Sabe Jones c50ed843fb Merge branch 'release' into sabrecat/postfixes 2024-06-27 10:03:30 -05:00
Sabe Jones 760c05df5d 5.25.8 2024-06-26 12:36:32 -05:00
Phillip Thelen 26d070f2c3 improve loggly request logging call (#15259) 2024-06-26 12:30:13 -05:00
Sabe Jones 850ae5114f fix(schedule): add Oddballs to November 2024-06-24 13:27:13 -05:00
Petal Forrest bc9577439e Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (286 of 286 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/en_GB/
2024-06-22 10:40:51 +02:00
Petal Forrest 10cd596f0b Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (427 of 427 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
2024-06-22 10:40:50 +02:00
Toro Mor d180062ad2 Translated using Weblate (German)
Currently translated at 91.9% (2852 of 3103 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
2024-06-22 10:40:50 +02:00
Petal Forrest bfacf4b36e Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (259 of 259 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
2024-06-22 10:40:50 +02:00
Weblate 2912f31dec Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 76.1% (325 of 427 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 97.3% (184 of 189 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 97.3% (184 of 189 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 99.8% (772 of 773 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 96.8% (183 of 189 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 79.6% (691 of 868 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (167 of 167 strings)

Co-authored-by: Petal Forrest <adrijanaskar2008backup@gmail.com>
Co-authored-by: Razi H <razi.haj@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/character/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/content/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Front
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-06-22 01:12:00 +02:00
Sabe Jones c47b287a89 5.25.7 2024-06-21 13:28:34 -05:00
Sabe Jones 3aa626d2ae fix(sprites): remove potion PNG override 2024-06-21 13:28:29 -05:00
Sabe Jones 647ee2a073 5.25.6 2024-06-21 11:56:07 -05:00
Sabe Jones 2080c3f7b8 fix(content): restore Fungi Potions and Snail Armor 2024-06-21 11:56:01 -05:00
Sabe Jones 6f65c72921 5.25.5 2024-06-21 10:14:57 -05:00
Sabe Jones 22bbdd6a28 Merge branch 'develop' into release 2024-06-21 10:14:54 -05:00
Phillip Thelen 1a3d6f6520 fix mage gear being shown as twohanded (#15254) 2024-06-21 10:14:25 -05:00
Sabe Jones aa1e78ac94 5.25.4 2024-06-21 09:59:08 -05:00
Sabe Jones 858caa4582 fix(cShop): price display for animal pieces 2024-06-21 09:58:33 -05:00
Sabe Jones a7e1091f3f 5.25.3 2024-06-21 09:13:32 -05:00
Sabe Jones 78fca804b7 fix(content): repair Rose Gold Potion and Beach Umbrella 2024-06-21 09:12:18 -05:00
Sabe Jones a919ef99fe 5.25.2 2024-06-21 07:26:01 -05:00
Sabe Jones de9ca06607 fix(gear): fill special data for seasonal body accessories 2024-06-21 07:25:49 -05:00
Sabe Jones b5c5990e56 5.25.1 2024-06-21 07:22:23 -05:00
Sabe Jones 756af8aafb fix(gear): fill special data for seasonal eyewear 2024-06-21 07:21:55 -05:00
Sabe Jones 54f84af274 5.25.0 2024-06-21 07:00:58 -05:00
Sabe Jones c151b6e1bc chore(subproj): update habitica-images 2024-06-21 07:00:55 -05:00
Sabe Jones effd729222 Merge branch 'schedule-rc' into develop 2024-06-21 06:52:46 -05:00
Weblate b7cdbc5c94 Translated using Weblate (Indonesian)
Currently translated at 95.7% (223 of 233 strings)

Translated using Weblate (Indonesian)

Currently translated at 98.3% (179 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Indonesian)

Currently translated at 76.0% (197 of 259 strings)

Translated using Weblate (German)

Currently translated at 91.8% (2850 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Indonesian)

Currently translated at 74.5% (193 of 259 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2848 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 99.2% (3080 of 3103 strings)

Translated using Weblate (German)

Currently translated at 91.6% (2844 of 3103 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (3037 of 3103 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.3% (862 of 868 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.2% (164 of 167 strings)

Translated using Weblate (German)

Currently translated at 91.5% (2840 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (German)

Currently translated at 91.4% (2838 of 3103 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Japanese)

Currently translated at 98.8% (3067 of 3103 strings)

Translated using Weblate (German)

Currently translated at 91.3% (2836 of 3103 strings)

Translated using Weblate (Croatian)

Currently translated at 77.8% (102 of 131 strings)

Translated using Weblate (Korean)

Currently translated at 96.8% (183 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 98.4% (186 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 53.2% (124 of 233 strings)

Translated using Weblate (Korean)

Currently translated at 81.9% (77 of 94 strings)

Translated using Weblate (Croatian)

Currently translated at 67.0% (63 of 94 strings)

Translated using Weblate (Korean)

Currently translated at 51.6% (47 of 91 strings)

Translated using Weblate (Croatian)

Currently translated at 40.6% (37 of 91 strings)

Translated using Weblate (Croatian)

Currently translated at 52.1% (135 of 259 strings)

Translated using Weblate (Slovak)

Currently translated at 51.0% (119 of 233 strings)

Translated using Weblate (Danish)

Currently translated at 63.0% (147 of 233 strings)

Translated using Weblate (Bulgarian)

Currently translated at 68.6% (160 of 233 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Turkish)

Currently translated at 80.9% (106 of 131 strings)

Translated using Weblate (Serbian)

Currently translated at 75.5% (99 of 131 strings)

Translated using Weblate (Slovak)

Currently translated at 75.5% (99 of 131 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Polish)

Currently translated at 90.8% (388 of 427 strings)

Translated using Weblate (Bulgarian)

Currently translated at 55.0% (1709 of 3103 strings)

Translated using Weblate (Spanish)

Currently translated at 84.5% (159 of 188 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Swedish)

Currently translated at 40.6% (37 of 91 strings)

Translated using Weblate (Serbian)

Currently translated at 39.5% (36 of 91 strings)

Translated using Weblate (Slovak)

Currently translated at 39.5% (36 of 91 strings)

Translated using Weblate (Romanian)

Currently translated at 40.6% (37 of 91 strings)

Translated using Weblate (Portuguese)

Currently translated at 52.7% (48 of 91 strings)

Translated using Weblate (Italian)

Currently translated at 49.4% (45 of 91 strings)

Translated using Weblate (Hebrew)

Currently translated at 38.4% (35 of 91 strings)

Translated using Weblate (Czech)

Currently translated at 40.6% (37 of 91 strings)

Translated using Weblate (Bulgarian)

Currently translated at 39.5% (36 of 91 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Serbian)

Currently translated at 86.3% (95 of 110 strings)

Translated using Weblate (Italian)

Currently translated at 92.7% (805 of 868 strings)

Translated using Weblate (Swedish)

Currently translated at 53.6% (139 of 259 strings)

Translated using Weblate (Serbian)

Currently translated at 52.8% (137 of 259 strings)

Translated using Weblate (Slovak)

Currently translated at 51.7% (134 of 259 strings)

Translated using Weblate (Hebrew)

Currently translated at 49.4% (128 of 259 strings)

Co-authored-by: Alberto jor avondet <albertoavondet@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Dragan SIRET <orion.siret0.2@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/character/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/he/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ro/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/sv/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/death/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/front/id/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/he/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/he/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/id/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sv/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/da/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/id/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/sk/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Groups
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Quests
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-06-21 12:33:19 +02:00
Phillip Thelen 28193f86fb Fix serving memoized content 2024-06-21 11:12:18 +02:00
Phillip Thelen 877fe48225 correctly memoize conent api 2024-06-20 12:23:24 +02:00
Phillip Thelen e0f6f79c5b don’t build multiple times on heroku 2024-06-20 10:11:27 +02:00
Phillip Thelen f62254d68e fix client command 2024-06-19 19:40:20 +02:00
Phillip Thelen d054e6fc16 correct build call 2024-06-19 19:36:57 +02:00
Phillip Thelen 7231f699c1 try setting up with heroku buildpack 2024-06-19 19:32:32 +02:00
Phillip Thelen 1dae0793fd call gulp build:prod 2024-06-19 18:50:32 +02:00
Phillip Thelen f18fbe86b6 build client 2024-06-19 18:40:53 +02:00
Phillip Thelen 61a61724ca testing 2024-06-19 18:33:18 +02:00
Phillip Thelen 93cf30eb18 integration fix 2024-06-19 18:20:43 +02:00
Phillip Thelen 379f41ff04 integration fix 2024-06-19 18:20:25 +02:00
Phillip Thelen cff08adcd0 specify dev docker file 2024-06-19 18:13:20 +02:00
Phillip Thelen 71936c1f0a initialize stub 2024-06-19 18:10:16 +02:00
Phillip Thelen 4da2ed4a1f initialize stub 2024-06-19 18:10:07 +02:00
Phillip Thelen 11c5b26c59 test heroku file 2024-06-19 18:08:45 +02:00
Phillip Thelen 19c79ce510 fix buy test 2024-06-19 18:04:01 +02:00
Phillip Thelen 0ba14c18b1 fix stub reference 2024-06-19 18:03:36 +02:00
Phillip Thelen ac85bb2e2d fix stub reference 2024-06-19 18:03:15 +02:00
Phillip Thelen 74dfb2710f test fixes 2024-06-19 18:02:28 +02:00
Phillip Thelen d2a0ab684a test fixes 2024-06-19 18:01:27 +02:00
Phillip Thelen 12d38fa813 fix content tests 2024-06-19 17:45:01 +02:00
Phillip Thelen 8dbd3c3db1 fix canOwn test 2024-06-19 17:37:04 +02:00
Phillip Thelen 74b3b348ff fix buy test 2024-06-19 17:32:31 +02:00
Phillip Thelen 3386d61fde fix debug tests 2024-06-19 17:30:52 +02:00
Phillip Thelen db41e00990 fix debug tests 2024-06-19 17:30:37 +02:00
Phillip Thelen 5d5275ce70 lint fixes 2024-06-19 17:30:29 +02:00
Phillip Thelen e39c63700e remove duplicate keys 2024-06-19 17:30:16 +02:00
Phillip Thelen 550ac2db9d fix tests 2024-06-19 17:14:32 +02:00
Phillip Thelen 19da14531c add chameleon to featured quests 2024-06-19 17:05:25 +02:00
Phillip Thelen 254dd80f24 fix import 2024-06-19 17:05:14 +02:00
Phillip Thelen 0bc3f16b4b add new content to new release file 2024-06-19 16:33:22 +02:00
Phillip Thelen 5184973bd5 fix release date tests 2024-06-19 16:33:11 +02:00
Phillip Thelen b6accca5ca fix armoire tests 2024-06-19 16:33:06 +02:00
Phillip Thelen fec68e6211 fix tests 2024-06-19 16:02:03 +02:00
Phillip Thelen fc63c906dd Improve test coverage 2024-06-19 15:25:45 +02:00
Phillip Thelen 3333f8f0f5 allow hatching potions to have a release date 2024-06-19 15:25:45 +02:00
Phillip Thelen 89a3ac3dde allow eggs to have a release date
# Conflicts:
#	test/content/armoire.test.js
2024-06-19 15:24:21 +02:00
CuriousMagpie 16551ec83f Merge branch '2024-07-content-prebuild' into subs-private 2024-06-18 15:03:12 -04:00
CuriousMagpie 2645bf6023 update habitica images 2024-06-18 15:02:47 -04:00
CuriousMagpie f5f4974a73 update habitica-images 2024-06-18 14:58:13 -04:00
CuriousMagpie 162e337d14 Merge branch '2024-07-content-prebuild' into subs-private 2024-06-18 13:46:03 -04:00
CuriousMagpie 21a7d36b7b update sprites 2024-06-18 13:45:09 -04:00
CuriousMagpie f2506c3231 updated sprites css 2024-06-18 13:24:21 -04:00
CuriousMagpie d47641e25a typo fix 2024-06-18 12:46:59 -04:00
Sabe Jones 983e01cb3f chore(sprites): update css 2024-06-17 15:02:23 -05:00
Sabe Jones a55ede9175 fix(migration): couple of vet pet issues 2024-06-17 14:45:57 -05:00
CuriousMagpie fb8479ad1e finish July prebuild 2024-06-17 13:44:36 -04:00
Sabe Jones 28491cb01d fix(content): Umbrella is PER not STR 2024-06-17 10:47:22 -05:00
Sabe Jones b49dddeb47 fix(shops): post merge cleanup 2024-06-14 18:12:22 -05:00
Sabe Jones 31e501f65a Merge branch 'release' into schedule-rc 2024-06-14 10:18:16 -05:00
CuriousMagpie 3810cf3ef3 add chameleon quest 2024-06-14 10:42:47 -04:00
Sabe Jones 050c227e6f 5.25.2 2024-06-14 08:54:38 -05:00
Weblate ddb8725052 Merge branch 'origin/develop' into Weblate. 2024-06-14 15:53:32 +02:00
Weblate e11b9ebe26 Translated using Weblate (Japanese)
Currently translated at 98.8% (3066 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (German)

Currently translated at 91.3% (2834 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3103 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 97.2% (3018 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Japanese)

Currently translated at 98.0% (851 of 868 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Korean)

Currently translated at 71.8% (624 of 868 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (German)

Currently translated at 91.2% (2832 of 3103 strings)

Translated using Weblate (Italian)

Currently translated at 4.2% (8 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Danish)

Currently translated at 87.2% (96 of 110 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (French)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Japanese)

Currently translated at 97.2% (3018 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (763 of 773 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (763 of 773 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Russian)

Currently translated at 98.2% (853 of 868 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Korean)

Currently translated at 91.4% (128 of 140 strings)

Translated using Weblate (Korean)

Currently translated at 79.8% (617 of 773 strings)

Translated using Weblate (Korean)

Currently translated at 75.0% (6 of 8 strings)

Translated using Weblate (Korean)

Currently translated at 70.7% (302 of 427 strings)

Translated using Weblate (Korean)

Currently translated at 51.6% (47 of 91 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3103 of 3103 strings)

Co-authored-by: Dragan SIRET <orion.siret0.2@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: taesub park <dungfly75@gmail.com>
Co-authored-by: Юрий Артамонов <zilberstein2211@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/da/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/death/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ko/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-06-14 15:53:21 +02:00
Phillip Thelen 4da53f83c9 Add option to log every request start and end to loggly (#15243) 2024-06-14 08:51:10 -05:00
CuriousMagpie d05da3722c add June background notes 2024-06-13 17:12:43 -04:00
CuriousMagpie b8a3440ef2 fix mystery item and background description 2024-06-13 16:40:04 -04:00
CuriousMagpie 44d63032d8 add subscriber gear, enchanted armoire, and background 2024-06-13 15:38:23 -04:00
CuriousMagpie 9d7da91ec6 add sprites 2024-06-13 14:44:59 -04:00
Phillip Thelen 39add61618 Revert "lint"
This reverts commit 544d67e7e5.
2024-06-11 19:10:50 +02:00
Sabe Jones 1c1543f012 fix(layout): tighten up various margins 2024-06-11 12:04:21 -05:00
Phillip Thelen 10a27354bb REVERT ._. 2024-06-11 19:00:34 +02:00
Phillip Thelen a00f199d18 Revert "Revert "allow hatching potions to have a release date""
This reverts commit 485584c144.

# Conflicts:
#	website/common/script/content/hatching-potions.js
2024-06-11 18:54:39 +02:00
Phillip Thelen 6c5bff7843 put armoire test back 2024-06-11 18:20:48 +02:00
Phillip Thelen 388c3d38ed Revert "Improve test coverage"
This reverts commit d4ba96796c.
2024-06-11 18:19:29 +02:00
Phillip Thelen 485584c144 Revert "allow hatching potions to have a release date"
This reverts commit ebdac0b388.
2024-06-11 18:19:21 +02:00
Phillip Thelen b83f62bd82 Revert "allow eggs to have a release date"
This reverts commit 6d13a257dd.
2024-06-11 18:19:11 +02:00
Phillip Thelen 544d67e7e5 lint 2024-06-10 14:53:23 +02:00
Phillip Thelen 1f0a4dad23 fix content caching 2024-06-10 14:46:39 +02:00
Phillip Thelen eb3220c96b fix lint 2024-06-10 14:46:32 +02:00
Phillip Thelen d4ba96796c Improve test coverage 2024-06-10 14:44:21 +02:00
Phillip Thelen ebdac0b388 allow hatching potions to have a release date 2024-06-10 14:24:59 +02:00
Phillip Thelen 6d13a257dd allow eggs to have a release date 2024-06-10 14:11:38 +02:00
Phillip Thelen c2b370f4d3 handle error from invalid input 2024-06-10 11:23:46 +02:00
Phillip Thelen 3313584d60 actually clear out seeking field on user. Even when creating a party 2024-06-07 08:36:08 -05:00
Phillip Thelen b76585cce3 add additional way to get content switchover time for client 2024-06-07 11:10:48 +02:00
Sabe Jones 8d9af82521 fix(import): add missing nconf 2024-06-06 14:55:37 -05:00
Phillip Thelen dcf25c0b4a fix tests 2024-06-06 21:51:07 +02:00
Phillip Thelen 31036ad9e4 return actually correct quests 2024-06-06 21:51:07 +02:00
Phillip Thelen 1ba85b403f clear content api cache each day 2024-06-06 21:51:07 +02:00
Phillip Thelen c26f410cc3 add fallback for seasonal gear 2024-06-06 12:21:34 +02:00
Phillip Thelen 242e64cedc add content time offset to client 2024-06-06 10:56:56 +02:00
Sabe Jones a44418c4fa fix(backgrounds): correct event and tt loading 2024-06-05 08:14:13 -05:00
Phillip Thelen ea66e4e4a1 Add end dates to categories in customizationshop 2024-06-05 10:34:17 +02:00
Phillip Thelen 6889b65123 fix questshop featured items 2024-06-05 10:12:47 +02:00
Sabe Jones fd5bc8f0b9 fix(shops): various cleanup 2024-06-04 11:50:26 -05:00
Sabe Jones f33aff577c fix(build): skip some polyfills 2024-05-31 15:54:06 -05:00
Sabe Jones 9ba986f5e5 fix(layout): Justin positioning and shopdown 2024-05-31 15:37:00 -05:00
Sabe Jones bf46c798a6 fix(migration): log transaction for awarded Gems 2024-05-31 11:54:27 -05:00
Phillip Thelen 54b1afd5b4 make content releases use a given offset to the time. 2024-05-29 19:05:17 +02:00
Sabe Jones b501e06f27 fix(pets): finish Veteran Cactus 2024-05-29 09:03:36 -05:00
Sabe Jones 2062c68877 WIP(shops): better Fennec and bg loading, add migration 2024-05-28 17:57:13 -05:00
Sabe Jones 19521b1894 fix(avatar): render first bg addition and Justin 2024-05-24 17:19:49 -05:00
Sabe Jones 688e7181f0 fix(customizations): responsive updates 2024-05-23 17:50:16 -05:00
Sabe Jones a53a9be4b7 fix(css): flex gap is a thing 2024-05-22 15:53:24 -05:00
Sabe Jones 66c56225a4 fix(shops): border radius and typo 2024-05-22 13:41:49 -05:00
Sabe Jones cabc08c04b fix(lint): remove console log 2024-05-22 09:27:35 -05:00
Sabe Jones 9ae6063f78 fix(npm): move moment-locales to main deps 2024-05-22 09:23:30 -05:00
Sabe Jones 7936677fd8 WIP(shops): empty states and deselect UX 2024-05-22 09:16:13 -05:00
Phillip Thelen 88a0b57335 add end date to seasonal gear in market 2024-05-22 15:47:02 +02:00
Phillip Thelen 2303d5de32 Fix quest premium hatching potions in market 2024-05-22 12:21:51 +02:00
Phillip Thelen 1f5de1ab42 remove sinon and moment locales to shrink client bundle 2024-05-22 11:24:31 +02:00
Phillip Thelen 435047cace strip sinon, nise and bootstrap-vue icons 2024-05-21 20:18:57 +02:00
Sabe Jones c0d6338eba WIP(shops): safer debug mode 2024-05-21 09:11:18 -05:00
Phillip Thelen 36b589e92d make it easier to unequip backgrounds 2024-05-21 15:53:14 +02:00
Phillip Thelen 42cafbeaab make market return unlocked quest potions 2024-05-21 15:39:59 +02:00
Phillip Thelen 5b5d5a39a4 remove date 2024-05-21 14:52:01 +02:00
Sabe Jones 8d479e358d WIP(shops): more fixes 2024-05-17 20:29:14 -05:00
Sabe Jones 3bb1cceed1 chore(testing): DO NOT MERGE THIS 2024-05-17 14:14:04 -05:00
Sabe Jones 0756d36fb3 fix(admin): let panel break item 2024-05-17 14:13:48 -05:00
Phillip Thelen 57deadaa5c Fix purchasing some hatching potions 2024-05-17 15:44:08 +02:00
Phillip Thelen becdf640b5 fix mystery gear 2024-05-17 12:54:21 +02:00
Phillip Thelen 756e99c089 add more purchase related tests 2024-05-17 11:35:22 +02:00
Phillip Thelen 709a14fd51 add some spell related tests 2024-05-17 11:35:14 +02:00
Phillip Thelen 33181c0ac4 Add purchase related test cases 2024-05-17 10:57:33 +02:00
Phillip Thelen ee974dfa19 fix seasonal quest purchasing 2024-05-17 10:30:15 +02:00
Phillip Thelen b697598d75 fix showing seasonal gear in market 2024-05-17 10:03:59 +02:00
Sabe Jones 6e5b13668a WIP(shops): fixes and Hall of Heroes update 2024-05-16 18:49:56 -05:00
Phillip Thelen 4c13f3193e fix backgrounds not showing on time traveler shop 2024-05-16 20:38:58 +02:00
Phillip Thelen 15cea33c4b fix import 2024-05-16 20:29:03 +02:00
Phillip Thelen cac0a84763 Add some tests for time travelers content 2024-05-16 20:19:26 +02:00
Phillip Thelen c56c07a0f8 fix buying transformation items 2024-05-16 20:19:26 +02:00
Phillip Thelen 929778bdad fix code coverage 2024-05-16 20:19:26 +02:00
Phillip Thelen d6dba9767d fix set name 2024-05-16 20:19:26 +02:00
Sabe Jones 7e7ce44c77 fix(shops): lots of layout corrections 2024-05-15 17:28:39 -05:00
Phillip Thelen 4d38880249 Refactor armoire content to be cached by day 2024-05-15 16:51:09 +02:00
Phillip Thelen 46d164ddd1 Fix seasonal npc sprite 2024-05-15 13:18:32 +02:00
Phillip Thelen 31e4c51c3f Add June Subscriber Gear 2024-05-15 12:54:53 +02:00
Phillip Thelen eebfb81bd2 add armoire items for june 2024-05-15 12:51:01 +02:00
Phillip Thelen 06623991b3 lint 2024-05-15 12:17:18 +02:00
Phillip Thelen fe697898ee Fix featured items 2024-05-15 12:05:30 +02:00
Phillip Thelen f3fc14bd53 fix seasonal gear 2024-05-15 11:56:40 +02:00
Phillip Thelen 7f0b0a3909 fix seasonal gold price 2024-05-15 11:35:27 +02:00
Phillip Thelen 0f395bcc3e Fix some sets not showing 2024-05-15 11:35:19 +02:00
Phillip Thelen d366b2cde1 remove old date strings 2024-05-15 11:25:13 +02:00
Phillip Thelen 7d8611bae2 remove availability data from strings 2024-05-15 11:13:23 +02:00
Sabe Jones 17964c0ab7 fix(build): add assert, le sigh 2024-05-14 14:34:53 -05:00
Phillip Thelen 5b6cc23fb7 event code cleanup 2024-05-14 12:41:49 +02:00
Phillip Thelen efe8cff1ad fix seasonal gear filtering 2024-05-14 11:56:04 +02:00
Phillip Thelen 6591f6780c test fixes 2024-05-14 11:07:15 +02:00
Phillip Thelen 592c320d1d remove only 2024-05-14 11:07:05 +02:00
Phillip Thelen df641d0866 remove express.js 2024-05-14 11:06:27 +02:00
Phillip Thelen da4606df5e fix seasonal gear 2024-05-14 11:06:17 +02:00
Phillip Thelen ceb6b93dc1 Add background 2024-05-13 15:24:04 +02:00
Phillip Thelen e006f3f7de Add cactus veteran pet 2024-05-13 14:56:04 +02:00
Phillip Thelen 246cc25b6d add seasonal sets to schedule 2024-05-13 14:51:35 +02:00
Phillip Thelen a1bb61793b add sprite definitions 2024-05-13 14:51:29 +02:00
Phillip Thelen 6523ed08cd Add Koi Hatchingpotion 2024-05-13 14:49:39 +02:00
Phillip Thelen ab34257c03 Add Summer Splash Gear 2024-05-13 14:37:33 +02:00
Phillip Thelen 6afdffae92 fixes 2024-05-13 14:00:44 +02:00
Phillip Thelen c3b17e3db0 Squashed commit of the following:
commit 934b85d716
Author: Sabe Jones <sabe@habitica.com>
Date:   Thu May 9 09:27:28 2024 -0500

    5.24.2

commit c6df34a7fc
Author: Sabe Jones <sabe@habitica.com>
Date:   Thu May 9 09:27:24 2024 -0500

    chore(subproj): update habitica-images

commit c51c90ba41
Author: Sabe Jones <sabe@habitica.com>
Date:   Thu May 9 09:26:49 2024 -0500

    Squashed commit of the following:

    commit 7d6320ee2d6e1dac5ac025c188162cba35ed49bf
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Mon May 6 16:22:53 2024 -0500

        fix(faq): copy updates

    commit 234870a7b2bc3b23ba2a044a1010fdc9b417bc45
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Fri May 3 16:06:48 2024 -0500

        fix(faq): cleaner layout

    commit 06f162cc7a6a2b94b916ae0514b08ede09e7a2dc
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Tue Apr 30 17:21:50 2024 -0500

        feat(faq): Content Schedule notes

commit d3f420144c
Author: Weblate <noreply@weblate.org>
Date:   Thu May 9 16:22:33 2024 +0200

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (378 of 378 strings)

    Translated using Weblate (German)

    Currently translated at 82.6% (214 of 259 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (German)

    Currently translated at 97.4% (753 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (131 of 131 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (2 of 2 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (868 of 868 strings)

    Translated using Weblate (German)

    Currently translated at 75.2% (195 of 259 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (113 of 113 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (189 of 189 strings)

    Translated using Weblate (German)

    Currently translated at 97.1% (751 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (140 of 140 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (113 of 113 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (427 of 427 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (German)

    Currently translated at 96.2% (744 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (867 of 868 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 98.7% (229 of 232 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (378 of 378 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (140 of 140 strings)

    Translated using Weblate (German)

    Currently translated at 89.5% (2766 of 3089 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.5% (762 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (868 of 868 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (German)

    Currently translated at 89.3% (2760 of 3089 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (232 of 232 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (French)

    Currently translated at 99.9% (3088 of 3089 strings)

    Translated using Weblate (German)

    Currently translated at 89.1% (2754 of 3089 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (182 of 182 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.4% (761 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (868 of 868 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (868 of 868 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (232 of 232 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.4% (761 of 773 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (378 of 378 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.3% (862 of 868 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (427 of 427 strings)

    Translated using Weblate (German)

    Currently translated at 89.0% (2752 of 3089 strings)

    Translated using Weblate (German)

    Currently translated at 89.0% (2750 of 3089 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (232 of 232 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3089 of 3089 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (773 of 773 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (773 of 773 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 99.7% (377 of 378 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (378 of 378 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (378 of 378 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (868 of 868 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 88.9% (2747 of 3089 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 88.7% (2740 of 3089 strings)

    Translated using Weblate (German)

    Currently translated at 88.8% (2734 of 3077 strings)

    Translated using Weblate (Korean)

    Currently translated at 79.8% (131 of 164 strings)

    Translated using Weblate (Korean)

    Currently translated at 79.8% (131 of 164 strings)

    Co-authored-by: Finrod <963505255@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Lapin <sirocuro01@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Tetiana <merekka13@gmail.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: 박동훈 <creator98@naver.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/noscript/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Character
    Translation: Habitica/Content
    Translation: Habitica/Death
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Limited
    Translation: Habitica/Noscript
    Translation: Habitica/Npc
    Translation: Habitica/Pets
    Translation: Habitica/Questscontent
    Translation: Habitica/Settings
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 1567f1c283
Author: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
Date:   Tue May 7 17:21:06 2024 -0400

    remove dempendabot.yml (#15193)

commit 3e19b8aa96
Author: Sabe Jones <sabe@habitica.com>
Date:   Fri Apr 26 15:26:23 2024 -0500

    5.24.1

commit d1bc1ab05a
Merge: 2d4ee636ae 13149d4acf
Author: Sabe Jones <sabe@habitica.com>
Date:   Fri Apr 26 15:25:51 2024 -0500

    Merge branch 'develop' into release

commit 13149d4acf
Merge: 42964c91f3 4b796fae5d
Author: Weblate <noreply@weblate.org>
Date:   Fri Apr 26 22:24:45 2024 +0200

    Merge branch 'origin/develop' into Weblate.

commit 2d4ee636ae
Author: Sabe Jones <sabe@habitica.com>
Date:   Fri Apr 26 15:22:08 2024 -0500

    5.24.0

commit 42964c91f3
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Apr 26 22:15:18 2024 +0200

    Fix issue with gift sub processing (#15184)

    * Fix issue with gift sub processing

    * Update cron.js

commit de62207504
Author: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
Date:   Fri Apr 26 16:14:45 2024 -0400

    May 2024 Content Prebuild (#15185)

    * 2024-05 css update

    * add May subscriber items, enchanted armoire (text placeholders), potions, and quest bundles

    * typo correction

    * add May achievement

    * content fixes after local testing

    * canonical date fix

    * fix potion descriptions, add periods to background descriptions

    * fix canonical date

    * updated armoire items

    * fix stat display on item

    * Fixing merge conflicts

    * resolve merge conflicts

    * add leading zero to mp drain for mushroom quest

    * fix timezones

    * proofreading pass

    * fix linting errors

    * date fixes & linter fixes

    * correct armoire expression at end of file

    * fix(autolint): roll back Prettier change

    ---------

    Co-authored-by: Sabe Jones <sabe@habitica.com>

commit 4b796fae5d
Author: Weblate <noreply@weblate.org>
Date:   Fri Apr 26 11:41:11 2024 +0200

    Translated using Weblate (German)

    Currently translated at 88.7% (2731 of 3077 strings)

    Translated using Weblate (German)

    Currently translated at 88.6% (2729 of 3077 strings)

    Translated using Weblate (German)

    Currently translated at 88.6% (2727 of 3077 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 99.8% (860 of 861 strings)

    Translated using Weblate (German)

    Currently translated at 88.5% (2726 of 3077 strings)

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (German)

    Currently translated at 88.5% (2724 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (German)

    Currently translated at 98.9% (283 of 286 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 97.5% (160 of 164 strings)

    Translated using Weblate (German)

    Currently translated at 89.8% (257 of 286 strings)

    Translated using Weblate (German)

    Currently translated at 87.7% (251 of 286 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (860 of 861 strings)

    Translated using Weblate (German)

    Currently translated at 97.6% (841 of 861 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 98.8% (256 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 91.8% (392 of 427 strings)

    Translated using Weblate (German)

    Currently translated at 91.1% (389 of 427 strings)

    Translated using Weblate (German)

    Currently translated at 88.7% (379 of 427 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3077 of 3077 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (231 of 231 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (French)

    Currently translated at 99.1% (3050 of 3077 strings)

    Translated using Weblate (German)

    Currently translated at 88.0% (376 of 427 strings)

    Translated using Weblate (German)

    Currently translated at 98.7% (228 of 231 strings)

    Translated using Weblate (Dutch)

    Currently translated at 100.0% (113 of 113 strings)

    Translated using Weblate (Dutch)

    Currently translated at 84.5% (2602 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (164 of 164 strings)

    Translated using Weblate (Romanian)

    Currently translated at 92.8% (130 of 140 strings)

    Translated using Weblate (German)

    Currently translated at 97.8% (226 of 231 strings)

    Translated using Weblate (Romanian)

    Currently translated at 75.0% (6 of 8 strings)

    Translated using Weblate (Romanian)

    Currently translated at 96.6% (58 of 60 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (German)

    Currently translated at 95.2% (220 of 231 strings)

    Translated using Weblate (French)

    Currently translated at 98.7% (3040 of 3077 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (13 of 13 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.6% (761 of 764 strings)

    Translated using Weblate (German)

    Currently translated at 54.9% (50 of 91 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 98.7% (3037 of 3077 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3077 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.6% (761 of 764 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (189 of 189 strings)

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 99.3% (163 of 164 strings)

    Translated using Weblate (German)

    Currently translated at 94.8% (219 of 231 strings)

    Translated using Weblate (German)

    Currently translated at 84.2% (241 of 286 strings)

    Translated using Weblate (German)

    Currently translated at 51.6% (47 of 91 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 98.1% (161 of 164 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3072 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (286 of 286 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.6% (761 of 764 strings)

    Translated using Weblate (German)

    Currently translated at 49.4% (45 of 91 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 89.7% (253 of 282 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 2.1% (3 of 137 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3077 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.6% (761 of 764 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (427 of 427 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.6% (761 of 764 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (231 of 231 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (56 of 56 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3077 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.9% (756 of 764 strings)

    Translated using Weblate (German)

    Currently translated at 48.3% (44 of 91 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.0% (749 of 764 strings)

    Translated using Weblate (German)

    Currently translated at 97.3% (744 of 764 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (164 of 164 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (231 of 231 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (113 of 113 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (131 of 131 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (22 of 22 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (8 of 8 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3077 of 3077 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3071 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.1% (3051 of 3077 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (861 of 861 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (140 of 140 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (230 of 230 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (131 of 131 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (427 of 427 strings)

    Translated using Weblate (German)

    Currently translated at 86.6% (370 of 427 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3035 of 3035 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3035 of 3035 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (239 of 239 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (182 of 182 strings)

    Translated using Weblate (Russian)

    Currently translated at 29.9% (41 of 137 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 98.0% (749 of 764 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (764 of 764 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (377 of 377 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (German)

    Currently translated at 97.8% (836 of 854 strings)

    Co-authored-by: Antonio Spinelli <tonicospinelli@users.noreply.translate.habitica.com>
    Co-authored-by: Céu <marcel.ufscar@gmail.com>
    Co-authored-by: Ellen A M <ellen_a_m@hotmail.com>
    Co-authored-by: Finrod <963505255@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Julian Brito <hackoogamer0852@gmail.com>
    Co-authored-by: Luã Fhelyp Guimarães <fhelypg@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Tetiana <merekka13@gmail.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Χρήστος Joia <hristosjoia@gmail.com>
    Co-authored-by: Катя Скибицкая <katerrina9993@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/inventory/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/messages/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Character
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Content
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Inventory
    Translation: Habitica/Limited
    Translation: Habitica/Loginincentives
    Translation: Habitica/Messages
    Translation: Habitica/Npc
    Translation: Habitica/Overview
    Translation: Habitica/Pets
    Translation: Habitica/Quests
    Translation: Habitica/Questscontent
    Translation: Habitica/Rebirth
    Translation: Habitica/Settings
    Translation: Habitica/Spells
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 2e9573ef92
Author: Yeah Jack <95103974+Yeah-Jack@users.noreply.github.com>
Date:   Thu Apr 25 21:25:33 2024 +0200

    Update README.md for better grammar (#15103)

commit 384bfce3eb
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:34:51 2024 -0400

    chore(deps): bump express from 4.18.2 to 4.19.2 in /website/client (#15189)

    Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
    - [Release notes](https://github.com/expressjs/express/releases)
    - [Changelog](https://github.com/expressjs/express/blob/master/History.md)
    - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

    ---
    updated-dependencies:
    - dependency-name: express
      dependency-type: indirect
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 5a8c7fb924
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:34:24 2024 -0400

    chore(deps): bump webpack-dev-middleware in /website/client (#15188)

    Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
    - [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
    - [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
    - [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

    ---
    updated-dependencies:
    - dependency-name: webpack-dev-middleware
      dependency-type: indirect
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 246775256e
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:34:01 2024 -0400

    chore(deps): bump express from 4.18.2 to 4.19.2 (#15190)

    Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
    - [Release notes](https://github.com/expressjs/express/releases)
    - [Changelog](https://github.com/expressjs/express/blob/master/History.md)
    - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

    ---
    updated-dependencies:
    - dependency-name: express
      dependency-type: direct:production
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit fa4cd8dd5a
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:32:59 2024 -0400

    chore(deps): bump tar from 6.2.0 to 6.2.1 (#15191)

    Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.0 to 6.2.1.
    - [Release notes](https://github.com/isaacs/node-tar/releases)
    - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/isaacs/node-tar/compare/v6.2.0...v6.2.1)

    ---
    updated-dependencies:
    - dependency-name: tar
      dependency-type: indirect
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 5224e063f7
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:32:36 2024 -0400

    chore(deps): bump axios from 0.21.4 to 1.6.8 (#15192)

    Bumps [axios](https://github.com/axios/axios) from 0.21.4 to 1.6.8.
    - [Release notes](https://github.com/axios/axios/releases)
    - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
    - [Commits](https://github.com/axios/axios/compare/v0.21.4...v1.6.8)

    ---
    updated-dependencies:
    - dependency-name: axios
      dependency-type: indirect
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit e5e8b9a7ec
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:19:21 2024 -0400

    build(deps): bump chai from 4.3.7 to 5.1.0 in /website/client (#15144)

    Bumps [chai](https://github.com/chaijs/chai) from 4.3.7 to 5.1.0.
    - [Release notes](https://github.com/chaijs/chai/releases)
    - [Changelog](https://github.com/chaijs/chai/blob/main/History.md)
    - [Commits](https://github.com/chaijs/chai/compare/v4.3.7...v5.1.0)

    ---
    updated-dependencies:
    - dependency-name: chai
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 7cd76c50eb
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:18:07 2024 -0400

    build(deps): bump axios from 0.27.2 to 0.28.0 in /website/client (#15148)

    Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 0.28.0.
    - [Release notes](https://github.com/axios/axios/releases)
    - [Changelog](https://github.com/axios/axios/blob/v0.28.0/CHANGELOG.md)
    - [Commits](https://github.com/axios/axios/compare/v0.27.2...v0.28.0)

    ---
    updated-dependencies:
    - dependency-name: axios
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit b520202544
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:17:17 2024 -0400

    build(deps): bump sass-loader from 8.0.2 to 14.1.1 in /website/client (#15159)

    Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 8.0.2 to 14.1.1.
    - [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
    - [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/webpack-contrib/sass-loader/compare/v8.0.2...v14.1.1)

    ---
    updated-dependencies:
    - dependency-name: sass-loader
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit bbae882eda
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Apr 25 14:12:46 2024 -0400

    chore(deps): bump follow-redirects in /website/client (#15179)

    Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.6.
    - [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
    - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.6)

    ---
    updated-dependencies:
    - dependency-name: follow-redirects
      dependency-type: indirect
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit ee93c8bec5
Author: Sabe Jones <sabe@habitica.com>
Date:   Mon Apr 8 15:49:20 2024 -0500

    5.23.0

commit c65e93e514
Author: Sabe Jones <sabe@habitica.com>
Date:   Mon Apr 8 15:49:17 2024 -0500

    chore(git): update subproject

commit 0fd808727c
Author: Sabe Jones <sabe@habitica.com>
Date:   Mon Apr 8 15:48:32 2024 -0500

    Squashed commit of the following:

    commit 3c3787091a2e8a94857352c3655f60138a3b20b7
    Merge: 76a00d6308 76d7f02fe8
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Mon Apr 8 15:47:35 2024 -0500

        Merge branch 'release' into 2024-04-april-fool-items

    commit 76a00d6308997c50ae5f5e9d6170a09a1a8cbac7
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Thu Mar 28 16:08:39 2024 -0500

        fix(quest): revise rage text

    commit c2e13f8af245993f61eb614d6be609c833c2e711
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Thu Mar 28 12:57:17 2024 -0500

        fix(quest): correct rage and "guscompletion"

    commit ecdeb82df998ab75eceae3e877c1758187b1d22c
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Thu Mar 28 08:55:18 2024 -0500

        fix(events): correct dates and times

    commit 232de436bbe7ba4c13f04ed66cc3b8a4a794d884
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Wed Mar 27 09:03:06 2024 -0500

        fix(content): a few more unruly Fungus

    commit 955e6e73387788797d1a83a037f73aa472515cec
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Wed Mar 27 09:00:03 2024 -0500

        feat(content): wacky potions 2024 by @CuriousMagpie

    commit 34b72be4f447f00c31169c4cd6e8ba79655adf8a
    Author: Sabe Jones <sabe@habitica.com>
    Date:   Wed Mar 27 08:56:27 2024 -0500

        feat(event): AF by @CuriousMagpie
2024-05-13 12:29:40 +02:00
Sabe Jones b9e128b387 feat(customizations): more icons 2024-05-10 15:37:07 -05:00
Sabe Jones cac14ab2cc Merge branch 'sabrecat/subs-private' into subs-private 2024-05-10 08:55:16 -05:00
Phillip Thelen c64a6eb66e remove log 2024-05-10 13:11:05 +02:00
Phillip Thelen cd33a539cf fix error in time travelers shop 2024-05-10 13:09:30 +02:00
Phillip Thelen ec76757f93 remove log 2024-05-09 14:38:16 +02:00
Phillip Thelen 03adff80f7 fix seasonal gear not showing as available 2024-05-09 13:55:39 +02:00
Phillip Thelen a73abcca74 don’t return event data anymore for items 2024-05-09 13:55:24 +02:00
Phillip Thelen a8c8fffa7c make sure schedule end dates are set as utc 2024-05-09 13:54:59 +02:00
Phillip Thelen f835cf2761 fix world state 2024-05-09 12:02:23 +02:00
Phillip Thelen 96fed21fbd add gear field to gala events 2024-05-09 10:45:37 +02:00
Phillip Thelen 7d62c87de3 optimize world state api call 2024-05-09 10:45:37 +02:00
Sabe Jones b28251dc9e WIP(shops): more layouts and fixes 2024-05-08 20:27:27 -05:00
Phillip Thelen b713e10c14 lint fixes 2024-05-08 17:48:34 +02:00
Phillip Thelen fce5371fce fix typos 2024-05-08 17:46:40 +02:00
Phillip Thelen a9cefd284a support april fools resale in schedule 2024-05-08 17:42:40 +02:00
Phillip Thelen 6ed422cd28 Improve repeating events handling 2024-05-08 17:42:24 +02:00
Phillip Thelen 02914685dc improve armoire release test cases 2024-05-08 17:05:09 +02:00
Phillip Thelen 856ed24dcb Add tests to check for correct food content populating 2024-05-08 16:46:42 +02:00
Phillip Thelen 4a9ec734c1 validate that schedule keys refer to existing content 2024-05-08 16:06:51 +02:00
Sabe Jones 61d151d2bb chore(testing): enable non prod analytics 2024-05-07 20:15:16 -05:00
Sabe Jones 32cb201b81 WIP(schedule): add June 2024 content 2024-05-07 19:36:39 -05:00
Sabe Jones 44a7006295 fix(backgrounds): correct wrapping 2024-05-07 17:19:14 -05:00
Sabe Jones 21a0bf7d65 WIP(shops): update avatar modal style 2024-05-07 09:06:23 -05:00
Phillip Thelen 0089506165 Fix setting end date 2024-05-07 15:17:22 +02:00
Phillip Thelen fbce5aae32 fix buying animal gear 2024-05-07 14:50:12 +02:00
Sabe Jones a8726eee0b fix(faq): copy updates 2024-05-06 16:23:33 -05:00
Sabe Jones 9efe370d33 fix(faq): cleaner layout 2024-05-03 16:07:35 -05:00
Phillip Thelen 47df62e716 remove logs 2024-05-03 11:14:49 +02:00
Phillip Thelen dac792dd27 add end date to more items/categories 2024-05-03 11:14:23 +02:00
Phillip Thelen eacf6de19a fix warning 2024-05-03 11:13:52 +02:00
Sabe Jones c1ca4e84b8 fix(layouts): May 2 updates 2024-05-03 00:14:33 -05:00
Phillip Thelen 87fc01cb81 improve armoire release process 2024-05-02 14:37:14 +02:00
Phillip Thelen 71f21c643c remove only 2024-05-02 14:37:14 +02:00
Phillip Thelen 1ced4a18d6 add handling and tests for new timetravelers schedule 2024-05-02 14:37:14 +02:00
Phillip Thelen 9dabe79d5e Add handling and tests for new background schedule 2024-05-02 14:37:14 +02:00
Phillip Thelen 4c2cdfe5b8 fix customization shop display on mobile 2024-05-02 14:37:14 +02:00
Sabe Jones f05888b116 fix(time-travelers): add countdowns 2024-04-30 17:26:22 -05:00
Sabe Jones ae8607c0c3 feat(faq): Content Schedule notes 2024-04-30 17:22:59 -05:00
Phillip Thelen d6cabeedb4 fix tests 2024-04-26 13:44:51 +02:00
Phillip Thelen 99a7b90247 Fix linting issues 2024-04-26 13:15:30 +02:00
Phillip Thelen fbdaa50fcf handle schedule end date for galas 2024-04-26 13:03:34 +02:00
Phillip Thelen 30e81297da Add comments to clarify schedule 2024-04-26 12:18:26 +02:00
Phillip Thelen b373eaff39 Fix merge issue 2024-04-26 12:09:04 +02:00
Phillip Thelen 80a212683d Fix assigning end date to content schedule items 2024-04-26 12:09:04 +02:00
Sabe Jones b0a4ed30d4 WIP(shops): dates and fixes 2024-04-26 12:08:30 +02:00
Phillip Thelen c2cb37ffe6 Cleanup pinned items that are no longer for purchase 2024-04-26 12:08:11 +02:00
Sabe Jones 558894fafd WIP(shops): cShop reconciled to schedule backend 2024-04-26 12:08:11 +02:00
Phillip Thelen 4bbdf27f48 add option to reset TT 2024-04-26 12:05:30 +02:00
Phillip Thelen daa296f2af fix some quests scheduling 2024-04-26 12:05:30 +02:00
Phillip Thelen 4c51212315 Cleanup pinned items that are no longer for purchase 2024-04-26 12:05:27 +02:00
Sabe Jones 372763c57c fix(shops): various 2024-04-24 17:58:20 -05:00
Sabe Jones 3bf323032c WIP(shops): first full test version 2024-04-24 00:02:41 -05:00
Sabe Jones 7a50b2d2ff Merge branch 'sabrecat/cshop' into subs-private 2024-04-23 10:52:05 -05:00
Sabe Jones 8df326bf92 WIP(shop): add timings to categories 2024-04-23 10:50:45 -05:00
Phillip Thelen 2d6555fe0f fix merge 2024-04-23 17:24:17 +02:00
Phillip Thelen b0d6f7722b allow non-admins to observe timetravel 2024-04-23 17:20:21 +02:00
Phillip Thelen aedbe7d333 content schedule tweaks 2024-04-23 17:20:21 +02:00
Phillip Thelen 6079dd4af6 feature items according to content schedule 2024-04-23 17:20:21 +02:00
Phillip Thelen 5c448188cf fix potion scheduling 2024-04-23 17:20:21 +02:00
Phillip Thelen d7dc878b1c Cleanup pinned items that are no longer for purchase 2024-04-23 17:20:20 +02:00
Phillip Thelen 7baec4e48e Fix assigning end date to content schedule items 2024-04-23 14:40:10 +02:00
Sabe Jones 5cd58d4119 WIP(shops): dates and fixes 2024-04-22 17:52:07 -05:00
Sabe Jones 4cdfefd92b Merge branch 'sabrecat/cshop' into subs-private 2024-04-19 20:12:35 -05:00
Sabe Jones 28b936e2d1 WIP(shops): cShop reconciled to schedule backend 2024-04-19 20:11:17 -05:00
Sabe Jones e4ec7e3e1e Merge branch 'gsf' into subs-private 2024-04-19 08:42:16 -05:00
Phillip Thelen 3c7ecef6a8 Update cron.js 2024-04-18 18:17:16 +02:00
Sabe Jones e2a5a1ab39 Merge branch 'sabrecat/cshop' into subs-private 2024-04-17 18:03:11 -05:00
Sabe Jones 5f64b2fb25 WIP(customizations): revised backgrounds modal 2024-04-17 18:02:35 -05:00
Sabe Jones f2a2d4cde5 Merge branch 'gsf' into subs-private 2024-04-17 09:05:36 -05:00
Phillip Thelen edc3c58876 Fix issue with gift sub processing 2024-04-05 13:30:15 +02:00
Sabe Jones 4277c08324 fix(scheduling): lint and code review 2024-04-04 22:42:28 -05:00
Sabe Jones 424f29a82b fix(lint): curly space 2024-04-04 22:02:31 -05:00
Sabe Jones e52c7ff9ce WIP(shop): empty states for categories 2024-04-04 22:02:19 -05:00
Sabe Jones fdc709d1c2 fix(lint): remove console log 2024-04-03 10:33:43 -05:00
Sabe Jones ea750571a0 fix(packages): add timers-browserify 2024-04-03 10:30:20 -05:00
Phillip Thelen f8fbea4654 quest shop fix 2024-04-03 16:06:08 +02:00
Phillip Thelen e0f4a4ecb8 merge in shop schedule loading 2024-04-03 16:06:08 +02:00
Phillip Thelen 5ce12d97be fix errors 2024-04-03 16:06:08 +02:00
Sabe Jones 4fdd064cd6 Merge branch 'sabrecat/customizations' into subs-private 2024-04-03 08:36:25 -05:00
Sabe Jones 37731b236a WIP(shop): add item names and unlocking 2024-04-02 17:58:06 -05:00
Sabe Jones b06c708480 Merge branch 'release' into sabrecat/customizations 2024-04-02 15:39:58 -05:00
Phillip Thelen f8c452ae3f Remove duplicate menu entry 2024-04-02 17:12:38 +02:00
Phillip Thelen 9befbec2b0 Update index.vue 2024-04-02 17:11:18 +02:00
Phillip Thelen 7b46f3bc23 Fix method name 2024-04-02 17:07:28 +02:00
Sabe Jones 64a500987c fix(packages): move sinon to main 2024-04-02 09:32:44 -05:00
Sabe Jones 92eaece5eb fix(packages): add assert 2024-04-01 19:13:29 -05:00
Sabe Jones ee73d5b628 fix(packages): add util 2024-04-01 19:10:59 -05:00
Sabe Jones a7eda1355b fix(packages): add sinon to client 2024-04-01 19:08:07 -05:00
Sabe Jones 8b9b79db8e fix(potions): remove EVENT 2024-04-01 19:04:57 -05:00
Sabe Jones 69c0488335 Merge branch 'sabrecat/customizations' into subs-private 2024-04-01 18:57:36 -05:00
Phillip Thelen c18e06f071 changes 2024-04-01 18:38:04 -05:00
Phillip Thelen a50c0eb1e7 fix test 2024-04-01 18:37:57 -05:00
Phillip Thelen fb56f7df20 Fix various tests 2024-04-01 18:37:46 -05:00
Phillip Thelen 3540a274b3 fix issue with seasonal quest scheduling 2024-04-01 18:36:44 -05:00
Phillip Thelen fe2c02679e Fix buying some items 2024-04-01 18:36:33 -05:00
Phillip Thelen bca3e96e9c Implement food seasons 2024-04-01 18:36:24 -05:00
Phillip Thelen 041edb3042 Preen old scheduling code 2024-04-01 18:13:40 -05:00
Phillip Thelen 6e96085f99 add cards to event content cycle 2024-04-01 18:07:41 -05:00
Phillip Thelen 249394b4ad Implement events throughout the year 2024-04-01 18:07:10 -05:00
Phillip Thelen 2a84561e00 fix some date issues with scheduling 2024-04-01 18:06:55 -05:00
Phillip Thelen 962456204e improve updating UI when time traveling 2024-04-01 18:06:00 -05:00
Phillip Thelen 593524905e allow admins to manipulate time on test servers 2024-04-01 18:05:41 -05:00
Phillip Thelen 1b12e9d8b7 proof of concept for time travel 2024-04-01 18:04:38 -05:00
Phillip Thelen 127f105934 typo 2024-04-01 17:56:24 -05:00
Phillip Thelen 2dfe5585eb fix 2024-04-01 17:56:15 -05:00
Phillip Thelen 93011f182f Fix lint and test 2024-04-01 17:56:06 -05:00
Phillip Thelen d11e95ab26 change mobile filter defaults to a list 2024-04-01 17:55:58 -05:00
Phillip Thelen f99ddbe60f display customizations in new shop 2024-04-01 17:54:07 -05:00
Phillip Thelen 982069df36 remove logs 2024-04-01 17:52:46 -05:00
Phillip Thelen db4bec37e3 Implement new schedule system for seasonal shop 2024-04-01 17:52:40 -05:00
Phillip Thelen 736ef16430 simplify schedule matching usage 2024-04-01 17:52:29 -05:00
Phillip Thelen 129cb7627c Implement new content schedule for magic hatching potions 2024-04-01 17:52:21 -05:00
Phillip Thelen f223b5dd2a Implement schedule for quest bundles 2024-04-01 17:52:12 -05:00
Phillip Thelen b3521be629 Implement new content schedule for potion and pet quests 2024-04-01 17:52:02 -05:00
Phillip Thelen 17db6a1772 Implement new release schedule for backgrounds 2024-04-01 17:51:47 -05:00
Phillip Thelen 278d9b74f9 Implement new scheduling for time travelers shop 2024-04-01 17:51:38 -05:00
Phillip Thelen ce796fa1d9 begin building recurring content scheduling 2024-04-01 17:51:24 -05:00
Phillip Thelen ec0275e6f6 Add tests for content filtering 2024-04-01 17:50:33 -05:00
Phillip Thelen 39252c7828 allow clients to filter content api call 2024-04-01 17:50:03 -05:00
Sabe Jones 75a88ab25a WIP(shops): style fixes 2024-03-29 16:14:14 -05:00
Sabe Jones 70434b17cc WIP(shop): show customs in buy modal 2024-03-14 17:29:15 -05:00
Sabe Jones a921a8bc61 Merge branch 'release' into sabrecat/customizations 2024-03-14 14:55:56 -05:00
Sabe Jones 0aa9d4d1d5 WIP(shops): start wiring up buy modals 2024-03-11 16:09:16 -05:00
Sabe Jones 0ead06937b Merge branch 'release' into sabrecat/customizations 2024-03-11 14:54:34 -05:00
Sabe Jones 037fb6737d Merge branch 'release' into sabrecat/customizations 2024-03-04 14:25:53 -06:00
Sabe Jones 4e0d8cba51 Merge branch 'release' into sabrecat/customizations 2024-03-01 14:31:28 -06:00
Sabe Jones ecc8a65d28 WIP(customizations): animal bits 2024-02-29 15:59:31 -06:00
Sabe Jones 28fef8df86 WIP(shops): shirts vs skins 2024-02-27 15:17:10 -06:00
Sabe Jones 33b54a734e WIP(shop): more CSS, add hair styles 2024-02-22 17:03:07 -06:00
Sabe Jones 1f8aa7d778 Merge branch 'release' into sabrecat/customizations 2024-02-22 15:26:01 -06:00
Sabe Jones 09ff3ee865 WIP(customizations): load hair colors 2024-02-20 17:56:56 -06:00
Sabe Jones cbfeb18517 WIP(shop): backgrounds appear 2024-02-16 17:58:11 -06:00
Sabe Jones 63e7ace693 WIP(shops): customization categories skeleton 2024-02-15 17:51:14 -06:00
Sabe Jones 0f9cf48b55 Merge branch 'release' into sabrecat/customizations 2024-02-15 15:26:05 -06:00
Sabe Jones 5a48436eff WIP(shops): customizations route 2024-02-14 17:49:26 -06:00
Sabe Jones 8b3267458b fix(groups): fix TypeError when missing 2024-02-05 18:06:18 -06:00
Sabe Jones bccfaab350 Merge branch 'release' into sabrecat/more-api-sunset 2024-02-05 17:28:11 -06:00
Sabe Jones 753f12979e fix(api): close lingering deprecated APIs 2024-02-05 17:22:47 -06:00
587 changed files with 19732 additions and 26604 deletions
+20 -10
View File
@@ -19,7 +19,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -41,7 +42,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -63,7 +65,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -86,7 +89,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -108,7 +112,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -137,7 +142,8 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -167,7 +173,8 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -197,7 +204,8 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -222,7 +230,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
@@ -246,7 +255,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
+2 -1
View File
@@ -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
+5 -2
View File
@@ -3,10 +3,13 @@ FROM node:20
# Install global packages
RUN npm install -g gulp-cli mocha
# Copy package.json and package-lock.json into image, then install
# dependencies.
# Copy package.json and package-lock.json into image
WORKDIR /usr/src/habitica
COPY ["package.json", "package-lock.json", "./"]
# Copy the remaining source files in.
COPY . /usr/src/habitica
# Install dependencies
RUN npm install
RUN npm run postinstall
RUN npm run client:build
RUN gulp build:prod
+12 -6
View File
@@ -1,14 +1,20 @@
Habitica ![Build Status](https://github.com/HabitRPG/habitica/workflows/Test/badge.svg) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
Habitica ![Build Status](https://github.com/HabitRPG/habitica/workflows/Test/badge.svg)
===============
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn money to buy weapons and armor.
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn Gold to buy weapons and armor!
**We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions:
**Want to contribute code to Habitica?** We're always looking for assistance on any issues in our repo with the "Help Wanted" label. The wiki pages below and the additional linked pages will tell you how to start contributing code and where you can seek further help or ask questions:
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
* [Setting up Habitica Locally](https://habitica.fandom.com/wiki/Setting_up_Habitica_Locally) - how to set up a local install of Habitica for development and testing on various platforms.
* [Setting up Habitica Locally](https://github.com/HabitRPG/habitica/wiki/Setting-Up-Habitica-for-Local-Development) - how to set up a local install of Habitica for development and testing.
**Interested in contributing to Habiticas mobile apps?** Visit the links below for our mobile repositories.
* **Android:** https://github.com/HabitRPG/habitica-android
* **iOS:** https://github.com/HabitRPG/habitica-ios
Habitica's code is licensed as described at https://github.com/HabitRPG/habitica/blob/develop/LICENSE
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than creating an issue (an admin will advise you if a new issue is necessary; usually it is not).
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than create an issue (an admin will advise you if a new issue is necessary; usually it is not).
**Have any questions about Habitica or its community?** See the links in the [habitica.com](https://habitica.com) website's Help menu or drop in to [Guilds > Tavern Chat](https://habitica.com/groups/tavern) to ask questions or chat socially!
**Creating a third-party tool?** Please review our [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines) to ensure that your tool is compliant and maintains the best experience for Habitica players.
**Have any questions about Habitica or contributing?** See the links in the [Habitica](https://habitica.com) website's Help menu. Theres FAQs, guides, and the option to reach out to us with any further questions!
+6 -1
View File
@@ -32,10 +32,12 @@
"LOGGLY_CLIENT_TOKEN": "token",
"LOGGLY_SUBDOMAIN": "example-subdomain",
"LOGGLY_TOKEN": "example-token",
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
"MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"MONGODB_POOL_SIZE": "10",
"MONGODB_SOCKET_TIMEOUT": "20000",
"NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
@@ -88,5 +90,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
}
+33 -2
View File
@@ -42,10 +42,41 @@ function cssVarMap (sprite) {
}
}
function createSpritesStream (name, src) {
function filterFile (file) {
if (file.relative.indexOf('Mount_Icon_') !== -1) {
return false;
}
if (file.path.indexOf('shop/') !== -1) {
return false;
}
if (file.path.indexOf('stable/eggs') !== -1) {
return false;
}
if (file.path.indexOf('stable/food') !== -1) {
return false;
}
if (file.path.indexOf('stable/potions') !== -1) {
return false;
}
if (file.relative.indexOf('shop_') === 0) {
return false;
}
if (file.relative.indexOf('icon_background') === 0) {
return false;
}
return true;
}
async function createSpritesStream (name, src) {
const stream = mergeStream();
// need to import this way bc of weird dependency things
// eslint-disable-next-line global-require
const filter = require('gulp-filter');
const f = filter(filterFile);
const spriteData = gulp.src(src)
.pipe(f)
.pipe(spritesmith({
imgName: `spritesmith-${name}.png`,
cssName: `spritesmith-${name}.css`,
@@ -63,7 +94,7 @@ function createSpritesStream (name, src) {
return stream;
}
gulp.task('sprites:main', () => {
gulp.task('sprites:main', async () => {
const mainSrc = sync('habitica-images/**/*.png');
return createSpritesStream('main', mainSrc);
});
+5 -5
View File
@@ -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,
),
));
@@ -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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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
}
};
@@ -0,0 +1,47 @@
/* eslint-disable no-console */
import { model as User } from '../../../website/server/models/user';
const MIGRATION_NAME = '2024_purge_invite_accepted';
const progressCount = 1000;
let count = 0;
async function updateUsers (userIds) {
count += userIds.length;
if (count % progressCount === 0) console.warn(`${count} ${userIds[0]}`);
return await User.updateMany(
{ _id: { $in: userIds } },
{ $pull: { notifications: { type: 'GROUP_INVITE_ACCEPTED' } } },
).exec();
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'notifications.type': 'GROUP_INVITE_ACCEPTED',
'auth.timestamps.loggedin': { $gt: new Date('2024-06-25') },
};
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({ _id: 1 })
.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],
};
}
const userIds = users.map(user => user._id);
await updateUsers(userIds); // eslint-disable-line no-await-in-loop
}
};
@@ -1,14 +1,12 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20230731_naming_day';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20240731_naming_day';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
count += 1;
let set;
let push;
@@ -115,16 +113,16 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
} else {
return await user.updateOne({ $set: set, $inc: inc }).exec();
return user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
}
return user.updateOne({ $set: set, $inc: inc }).exec();
}
export default async function processUsers () {
let query = {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') },
'auth.timestamps.loggedin': { $gt: new Date('2024-07-01') },
};
const fields = {
@@ -136,7 +134,7 @@ export default async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.sort({ _id: 1 })
.select(fields)
.exec();
@@ -152,4 +150,4 @@ export default async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};
}
+1575 -392
View File
File diff suppressed because it is too large Load Diff
+13 -10
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.25.1",
"version": "5.28.1",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -15,6 +15,7 @@
"amplitude": "^6.0.0",
"apidoc": "^0.54.0",
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"bootstrap": "^4.6.2",
@@ -35,6 +36,7 @@
"got": "^11.8.6",
"gulp": "^4.0.0",
"gulp-babel": "^8.0.0",
"gulp-filter": "^7.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-nodemon": "^2.5.0",
"gulp.spritesmith": "^6.13.0",
@@ -67,12 +69,14 @@
"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",
"useragent": "^2.1.9",
"uuid": "^9.0.0",
"validator": "^13.11.0",
"webpack-bundle-analyzer": "^4.10.2",
"winston": "^3.10.0",
"winston-loggly-bulk": "^3.3.0",
"xml2js": "^0.6.2"
@@ -93,11 +97,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",
@@ -106,22 +110,21 @@
"debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc"
"apidoc": "gulp apidoc",
"heroku-postbuild": "npm run client:build"
},
"devDependencies": {
"axios": "^1.4.0",
"axios": "^1.7.4",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"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"
}
+89
View File
@@ -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.rmSync(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`);
});
});
});
+1 -1
View File
@@ -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);
});
@@ -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;
});
});
@@ -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;
});
});
@@ -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;
});
});
@@ -54,6 +54,7 @@ describe('rateLimiter middleware', () => {
it('does not throw when there are available points', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
await attachRateLimiter(req, res, next);
@@ -71,6 +72,7 @@ describe('rateLimiter middleware', () => {
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
sandbox.stub(logger, 'error');
sandbox.stub(RateLimiterMemory.prototype, 'consume')
.returns(Promise.reject(new Error('Unknown error.')));
@@ -104,6 +106,7 @@ describe('rateLimiter middleware', () => {
it('limits when LIVELINESS_PROBE_KEY is incorrect', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.query.liveliness = 'das';
@@ -120,6 +123,7 @@ describe('rateLimiter middleware', () => {
it('limits when LIVELINESS_PROBE_KEY is not set', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
await attachRateLimiter(req, res, next);
@@ -135,6 +139,7 @@ describe('rateLimiter middleware', () => {
it('throws when LIVELINESS_PROBE_KEY is blank', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.query.liveliness = '';
@@ -150,6 +155,7 @@ describe('rateLimiter middleware', () => {
it('throws when there are no available points remaining', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
// call for 31 times
@@ -173,6 +179,7 @@ describe('rateLimiter middleware', () => {
it('uses the user id if supplied or the ip address', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.ip = 1;
@@ -199,4 +206,51 @@ describe('rateLimiter middleware', () => {
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('applies increased cost for registration calls with and without user id', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.path = '/api/v4/user/auth/local/register';
req.ip = 1;
await attachRateLimiter(req, res, next);
req.headers['x-api-user'] = 'user-1';
await attachRateLimiter(req, res, next);
await attachRateLimiter(req, res, next);
// user id an ip are counted as separate sources
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 27, // 2 calls with user id
'X-RateLimit-Reset': sinon.match(Date),
});
req.headers['x-api-user'] = undefined;
await attachRateLimiter(req, res, next);
await attachRateLimiter(req, res, next);
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 24, // 3 calls with only ip
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('applies increased cost for unauthenticated API calls', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.ip = 1;
await attachRateLimiter(req, res, next);
await attachRateLimiter(req, res, next);
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 10,
'X-RateLimit-Reset': sinon.match(Date),
});
});
});
@@ -0,0 +1,37 @@
/* eslint-disable global-require */
import requireAgain from 'require-again';
import {
generateRes,
generateReq,
generateNext,
} from '../../../helpers/api-unit.helper';
describe('requestLogHandler middleware', () => {
let res; let req; let
next;
const pathToMiddleWare = '../../../../website/server/middlewares/requestLogHandler';
beforeEach(() => {
res = generateRes();
req = generateReq();
next = generateNext();
});
it('attaches start time and request ID object to req', () => {
const middleware = requireAgain(pathToMiddleWare);
middleware.logRequestData(req, res, next);
expect(req.requestStartTime).to.exist;
expect(req.requestStartTime).to.be.a('number');
expect(req.requestIdentifier).to.exist;
expect(req.requestIdentifier).to.be.a('string');
});
it('calls next', () => {
const middleware = requireAgain(pathToMiddleWare);
const spy = sinon.spy();
middleware.logRequestData(req, res, spy);
expect(spy.calledOnce).to.be.true;
});
});
@@ -3,6 +3,7 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-integration/v3';
import { model as Group } from '../../../../../website/server/models/group';
describe('GET /groups/:groupId/chat', () => {
let user;
@@ -37,4 +38,34 @@ describe('GET /groups/:groupId/chat', () => {
});
});
});
context('public Guild', () => {
let group;
before(async () => {
({ group } = await createAndPopulateGroup({
groupDetails: {
name: 'test group',
type: 'guild',
privacy: 'private',
},
members: 1,
upgradeToGroupPlan: true,
chat: [
'Hello',
'Welcome to the Guild',
],
}));
// Creation API is shut down, we need to simulate an extant public group
await Group.updateOne({ _id: group._id }, { $set: { privacy: 'public' }, $unset: { 'purchased.plan': 1 } }).exec();
});
it('returns error if user attempts to fetch a sunset Guild', async () => {
await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('featureRetired'),
});
});
});
});
@@ -4,6 +4,7 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-integration/v3';
import { model as Group } from '../../../../../website/server/models/group';
describe('POST /chat/:chatId/like', () => {
let user;
@@ -111,4 +112,18 @@ describe('POST /chat/:chatId/like', () => {
message: t('groupNotFound'),
});
});
it('does not like a message that belongs to a sunset public group', async () => {
const message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
// Creation API is shut down, we need to simulate an extant public group
await Group.updateOne({ _id: groupWithChat._id }, { $set: { privacy: 'public' }, $unset: { 'purchased.plan': 1 } }).exec();
await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('featureRetired'),
});
});
});
@@ -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');
});
});
@@ -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.',
});
});
});
@@ -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({
@@ -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({
@@ -0,0 +1,73 @@
import nconf from 'nconf';
import {
generateUser,
createAndPopulateGroup,
} from '../../../../helpers/api-integration/v3';
describe('POST /debug/boss-rage', () => {
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(() => {
nconfStub.restore();
});
it('errors if user is not in a party', async () => {
await expect(user.post('/debug/boss-rage'))
.to.eventually.be.rejected.and.deep.equal({
code: 400,
error: 'BadRequest',
message: 'User not in a party.',
});
});
it('returns error when not in production mode', async () => {
nconfStub.withArgs('DEBUG_ENABLED').returns(false);
await expect(user.post('/debug/boss-rage'))
.to.eventually.be.rejected.and.deep.equal({
code: 404,
error: 'NotFound',
message: 'Not found.',
});
});
context('user is in a party', async () => {
let party;
beforeEach(async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Party',
type: 'party',
},
members: 2,
});
party = group;
user = groupLeader;
});
it('increases boss rage to 50', async () => {
await user.post('/debug/boss-rage');
await party.sync();
expect(party.quest.progress.rage).to.eql(50);
});
it('increases boss rage to 100', async () => {
await user.post('/debug/boss-rage');
await user.post('/debug/boss-rage');
await party.sync();
expect(party.quest.progress.rage).to.eql(100);
});
});
});
@@ -0,0 +1,86 @@
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);
const tomorrow = new Date(today.valueOf());
tomorrow.setDate(today.getDate() + 1);
expect(newResultDate.getDate()).to.eql(tomorrow.getDate());
expect(newResultDate.getMonth()).to.eql(tomorrow.getMonth());
expect(newResultDate.getFullYear()).to.eql(tomorrow.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);
const yesterday = new Date(today.valueOf());
yesterday.setDate(today.getDate() - 1);
expect(newResultDate.getDate()).to.eql(yesterday.getDate());
expect(newResultDate.getMonth()).to.eql(yesterday.getMonth());
expect(newResultDate.getFullYear()).to.eql(yesterday.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.',
});
});
});
@@ -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({
@@ -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({
@@ -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({
@@ -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({
@@ -85,22 +85,6 @@ describe('POST /group/:groupId/join', () => {
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1);
});
it('notifies inviting user that their invitation was accepted', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
const inviter = await user.get('/user');
const expectedData = {
headerText: t('invitationAcceptedHeader'),
bodyText: t('invitationAcceptedBody', {
username: invitedUser.auth.local.username,
groupName: guild.name,
}),
};
expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED');
expect(inviter.notifications[1].data).to.eql(expectedData);
});
it('awards Joined Guild achievement', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
@@ -155,23 +139,6 @@ describe('POST /group/:groupId/join', () => {
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
});
it('notifies inviting user that their invitation was accepted', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
const inviter = await user.get('/user');
const expectedData = {
headerText: t('invitationAcceptedHeader'),
bodyText: t('invitationAcceptedBody', {
username: invitedUser.auth.local.username,
groupName: party.name,
}),
};
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
expect(inviter.notifications[0].data).to.eql(expectedData);
});
it('clears invitation from user when joining party', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
@@ -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');
});
});
@@ -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 () => {
@@ -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({
@@ -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;
@@ -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', () => {
@@ -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 () => {
@@ -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('fall');
});
});
});
+67
View File
@@ -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;
});
});
+123
View File
@@ -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;
});
});
-219
View File
@@ -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);
});
});
});
});
});
});
+442
View File
@@ -0,0 +1,442 @@
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-01T09:00:00.000Z'));
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.length).to.eql(3);
});
it('does not contain past scheduled premium hatching potions', async () => {
clock = sinon.useFakeTimers(new Date('2024-04-01T09:00:00.000Z'));
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.filter(x => x.key === 'Aquatic' || x.key === 'Celestial').length, 'Aquatic or Celestial found').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-01T09:00:00.000Z'));
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.length).to.eql(3);
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);
});
it('does not add an end date to steampunk gear', () => {
const categories = shopCategories.filter(cat => cat.identifier.startsWith('30'));
categories.forEach(category => {
expect(category.end).to.not.exist;
category.items.forEach(item => {
expect(item.end).to.not.exist;
});
});
});
});
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);
}
});
});
});
});
});
+2 -2
View File
@@ -1,11 +1,11 @@
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 => {
if (type === 'all') return;
Object.keys(armoireSet[type]).forEach(itemKey => {
const ownedKey = `${type}_armoire_${itemKey}`;
expect(armoireSet[type][itemKey].canOwn({
items: {
gear: {
@@ -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);
@@ -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;
});
});
@@ -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');
}
});
});
});
@@ -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);
});
});
});
});
@@ -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;
-89
View File
@@ -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;
});
});
+172
View File
@@ -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'));
}
});
});
});
@@ -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,50 @@ 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 event hatching potion', async () => {
clock.restore();
clock = sandbox.useFakeTimers(moment('2022-04-10').valueOf());
const type = 'hatchingPotions';
const key = 'Veggie';
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 +286,6 @@ describe('shared.ops.purchase', () => {
expect(user.balance).to.equal(startingBalance - price);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
clock.restore();
});
});
@@ -257,5 +326,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'));
}
});
});
});
@@ -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'));
}
});
});
-27
View File
@@ -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');
};
};
+72
View File
@@ -0,0 +1,72 @@
/* eslint-disable global-require */
import forEach from 'lodash/forEach';
import {
expectValidTranslationString,
} from '../helpers/content.helper';
import armoire from '../../website/common/script/content/gear/sets/armoire';
describe('armoire', () => {
let clock;
afterEach(() => {
if (clock) {
clock.restore();
}
});
it('does not return unreleased gear', async () => {
clock = sinon.useFakeTimers(new Date('2024-01-02'));
const items = armoire.all;
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 = armoire.all;
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 = armoire.all;
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 = armoire.all;
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 = armoire.all;
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-07T09:00:00.000Z'));
const febuaryItems = armoire.all;
expect(febuaryItems.length).to.equal(384);
});
it('sets have at least 2 items', () => {
const setMap = {};
forEach(armoire.all, item => {
// Gotta have one outlier
if (!item.set || item.set.startsWith('armoire-')) return;
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);
});
});
});
+40 -18
View File
@@ -5,29 +5,51 @@ import {
expectValidTranslationString,
} from '../helpers/content.helper';
import * as eggs from '../../website/common/script/content/eggs';
import eggs from '../../website/common/script/content/eggs';
describe('eggs', () => {
describe('all', () => {
it('is a combination of drop and quest eggs', () => {
const dropNumber = Object.keys(eggs.drops).length;
const questNumber = Object.keys(eggs.quests).length;
const allNumber = Object.keys(eggs.all).length;
let clock;
expect(allNumber).to.be.greaterThan(0);
expect(allNumber).to.equal(dropNumber + questNumber);
});
afterEach(() => {
if (clock) {
clock.restore();
}
});
it('contains basic information about each egg', () => {
each(eggs.all, (egg, key) => {
expectValidTranslationString(egg.text);
expectValidTranslationString(egg.adjective);
expectValidTranslationString(egg.mountText);
expectValidTranslationString(egg.notes);
expect(egg.canBuy).to.be.a('function');
expect(egg.value).to.be.a('number');
expect(egg.key).to.equal(key);
const eggTypes = [
'drops',
'quests',
];
eggTypes.forEach(eggType => {
describe(eggType, () => {
it('contains basic information about each egg', () => {
each(eggs[eggType], (egg, key) => {
expectValidTranslationString(egg.text);
expectValidTranslationString(egg.adjective);
expectValidTranslationString(egg.mountText);
expectValidTranslationString(egg.notes);
expect(egg.canBuy).to.be.a('function');
expect(egg.value).to.be.a('number');
expect(egg.key).to.equal(key);
});
});
});
});
it('does not contain unreleased eggs', () => {
clock = sinon.useFakeTimers(new Date('2024-05-20'));
const questEggs = eggs.quests;
expect(questEggs.Giraffe).to.not.exist;
});
it('Releases eggs when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-05-20'));
const mayEggs = eggs.quests;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const juneEggs = eggs.quests;
expect(juneEggs.Giraffe).to.exist;
expect(Object.keys(mayEggs).length).to.equal(Object.keys(juneEggs).length - 1);
});
});
+40
View File
@@ -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');
});
});
+94
View File
@@ -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);
});
});
@@ -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;
+39 -17
View File
@@ -5,28 +5,50 @@ import {
expectValidTranslationString,
} from '../helpers/content.helper';
import * as hatchingPotions from '../../website/common/script/content/hatching-potions';
import hatchingPotions 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(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);
});
});
});
});
it('does not contain unreleased potions', () => {
clock = sinon.useFakeTimers(new Date('2024-05-20'));
const premiumPotions = hatchingPotions.premium;
expect(premiumPotions.Koi).to.not.exist;
});
it('Releases potions when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-05-20'));
const mayPotions = hatchingPotions.premium;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const junePotions = hatchingPotions.premium;
expect(junePotions.Koi).to.exist;
expect(Object.keys(mayPotions).length).to.equal(Object.keys(junePotions).length - 1);
});
});
+154
View File
@@ -0,0 +1,154 @@
import content from '../../website/common/script/content';
describe('content index', () => {
let clock;
afterEach(() => {
if (clock) {
clock.restore();
}
});
it('Releases eggs when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const mayEggs = content.eggs;
expect(mayEggs.Chameleon).to.not.exist;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-07-20'));
const juneEggs = content.eggs;
expect(juneEggs.Chameleon).to.exist;
expect(Object.keys(mayEggs).length, '').to.equal(Object.keys(juneEggs).length - 1);
});
it('Releases hatching potions when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-05-20'));
const mayHatchingPotions = content.hatchingPotions;
expect(mayHatchingPotions.Koi).to.not.exist;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const juneHatchingPotions = content.hatchingPotions;
expect(juneHatchingPotions.Koi).to.exist;
expect(Object.keys(mayHatchingPotions).length, '').to.equal(Object.keys(juneHatchingPotions).length - 1);
});
it('Releases armoire gear when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const juneGear = content.gear.flat;
expect(juneGear.armor_armoire_corsairsCoatAndCape).to.not.exist;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-07-10'));
const julyGear = content.gear.flat;
expect(julyGear.armor_armoire_corsairsCoatAndCape).to.exist;
expect(Object.keys(juneGear).length, '').to.equal(Object.keys(julyGear).length - 3);
});
it('Releases pets when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const junePets = content.petInfo;
expect(junePets['Chameleon-Base']).to.not.exist;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-07-18'));
const julyPets = content.petInfo;
expect(julyPets['Chameleon-Base']).to.exist;
expect(Object.keys(junePets).length, '').to.equal(Object.keys(julyPets).length - 10);
});
it('Releases mounts when appropriate without needing restarting', () => {
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const juneMounts = content.mountInfo;
expect(juneMounts['Chameleon-Base']).to.not.exist;
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-07-18'));
const julyMounts = content.mountInfo;
expect(julyMounts['Chameleon-Base']).to.exist;
expect(Object.keys(juneMounts).length, '').to.equal(Object.keys(julyMounts).length - 10);
});
it('marks regular food as buyable and droppable without any events', () => {
clock = sinon.useFakeTimers(new Date('2024-06-20'));
const { food } = content;
Object.keys(food).forEach(key => {
if (key === 'Saddle') {
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
return;
}
let expected = true;
if (key.startsWith('Cake_')) {
expected = false;
} else if (key.startsWith('Candy_')) {
expected = false;
} else if (key.startsWith('Pie_')) {
expected = false;
}
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
});
});
it('marks candy as buyable and droppable during habitoween', () => {
clock = sinon.useFakeTimers(new Date('2024-10-31'));
const { food } = content;
Object.keys(food).forEach(key => {
if (key === 'Saddle') {
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
return;
}
let expected = false;
if (key.startsWith('Cake_')) {
expected = false;
} else if (key.startsWith('Candy_')) {
expected = true;
} else if (key.startsWith('Pie_')) {
expected = false;
}
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
});
});
it('marks cake as buyable and droppable during birthday', () => {
clock = sinon.useFakeTimers(new Date('2024-01-31'));
const { food } = content;
Object.keys(food).forEach(key => {
if (key === 'Saddle') {
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
return;
}
let expected = false;
if (key.startsWith('Cake_')) {
expected = true;
} else if (key.startsWith('Candy_')) {
expected = false;
} else if (key.startsWith('Pie_')) {
expected = false;
}
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
});
});
it('marks pie as buyable and droppable during pi day', () => {
clock = sinon.useFakeTimers(new Date('2024-03-14'));
const { food } = content;
Object.keys(food).forEach(key => {
if (key === 'Saddle') {
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
return;
}
let expected = false;
if (key.startsWith('Cake_')) {
expected = false;
} else if (key.startsWith('Candy_')) {
expected = false;
} else if (key.startsWith('Pie_')) {
expected = true;
}
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
});
});
});
+82
View File
@@ -0,0 +1,82 @@
import find from 'lodash/find';
import maxBy from 'lodash/maxBy';
import {
ARMOIRE_RELEASE_DATES,
EGGS_RELEASE_DATES,
HATCHING_POTIONS_RELEASE_DATES,
} from '../../website/common/script/content/constants/releaseDates';
import armoire from '../../website/common/script/content/gear/sets/armoire';
import eggs from '../../website/common/script/content/eggs';
import hatchingPotions from '../../website/common/script/content/hatching-potions';
describe('releaseDates', () => {
let clock;
afterEach(() => {
if (clock) {
clock.restore();
}
});
describe('armoire', () => {
it('should only contain valid armoire names', () => {
const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-20`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-20`));
Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => {
expect(find(armoire.all, { set: key }), `${key} is not a valid armoire set`).to.exist;
});
});
it('should contain a valid year and month', () => {
Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => {
const date = ARMOIRE_RELEASE_DATES[key];
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
expect(date.year).to.be.at.least(2023);
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
expect(date.month).to.be.within(1, 12);
expect(date.day).to.not.exist;
});
});
});
describe('eggs', () => {
it('should only contain valid egg names', () => {
const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
Object.keys(EGGS_RELEASE_DATES).forEach(key => {
expect(eggs.all[key], `${key} is not a valid egg name`).to.exist;
});
});
it('should contain a valid year, month and date', () => {
Object.keys(EGGS_RELEASE_DATES).forEach(key => {
const date = EGGS_RELEASE_DATES[key];
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
expect(date.year).to.be.at.least(2024);
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
expect(date.month).to.be.within(1, 12);
expect(date.day, `${key} day is not a valid day`).to.be.a('number');
});
});
});
describe('hatchingPotions', () => {
it('should only contain valid potion names', () => {
const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => {
expect(hatchingPotions.all[key], `${key} is not a valid potion name`).to.exist;
});
});
it('should contain a valid year, month and date', () => {
Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => {
const date = HATCHING_POTIONS_RELEASE_DATES[key];
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
expect(date.year).to.be.at.least(2024);
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
expect(date.month).to.be.within(1, 12);
expect(date.day, `${key} day is not a valid day`).to.be.a('number');
});
});
});
});
+332
View File
@@ -0,0 +1,332 @@
// 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 potions 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;
let clock;
beforeEach(() => {
switchoverTime = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
clearCachedMatchers();
});
afterEach(() => {
if (clock) {
clock.restore();
}
});
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 before switchover', () => {
const date = new Date('2024-05-07T07:00:00.000+00:00');
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 after switchover', () => {
const date = new Date('2024-05-07T09:00:00.000+00:00');
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('sets the end date for a winter gala', () => {
const date = new Date('2024-12-22');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('uses correct date for first hours of the month', () => {
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the previous month
const date = new Date('2024-05-01T02:00:00.000Z');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.petQuests.items).to.contain('snake');
expect(matchers.petQuests.items).to.not.contain('horse');
expect(matchers.timeTravelers.match('202304'), '202304').to.be.true;
expect(matchers.timeTravelers.match('202404'), '202404').to.be.false;
expect(matchers.timeTravelers.match('202305'), '202305').to.be.false;
});
it('uses correct date after switchover time', () => {
// if the date is checked after CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the current
const date = new Date('2024-05-01T09:00:00.000Z');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.petQuests.items).to.contain('snake');
expect(matchers.petQuests.items).to.not.contain('horse');
expect(matchers.timeTravelers.match('202304'), '202304').to.be.false;
expect(matchers.timeTravelers.match('202305'), '202305').to.be.true;
expect(matchers.timeTravelers.match('202405'), '202405').to.be.false;
});
it('uses UTC timezone', () => {
// if the date is checked after CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the current
clock = sinon.useFakeTimers(new Date('2024-05-01T05:00:00.000-04:00'));
const matchers = getAllScheduleMatchingGroups();
expect(matchers.petQuests.items).to.contain('snake');
expect(matchers.petQuests.items).to.not.contain('horse');
expect(matchers.timeTravelers.match('202304'), '202304').to.be.false;
expect(matchers.timeTravelers.match('202305'), '202305').to.be.true;
expect(matchers.timeTravelers.match('202405'), '202405').to.be.false;
});
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(5);
expect(matchers.premiumHatchingPotions.items.indexOf('Veggie')).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(potions.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'), '202307').to.be.true;
expect(matcher.match('202207'), '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'), '202306').to.be.false;
expect(matcher.match('202402'), '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'), '202407').to.be.false;
});
it('disallows sets from the future', () => {
const date = new Date('2024-07-08');
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
expect(matcher.match('202507'), '202507').to.be.false;
});
it('matches sets released in the earlier half of the year', () => {
const date = new Date('2024-07-08');
const matcher = getAllScheduleMatchingGroups(date).timeTravelers;
expect(matcher.match('202401'), '202401').to.be.true;
});
});
});
+52
View File
@@ -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);
});
});
});
+63
View File
@@ -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);
}
}
});
});
+12 -3
View File
@@ -6,12 +6,21 @@ import {
} from '../helpers/content.helper';
import t from '../../website/common/script/content/translation';
import * as stable from '../../website/common/script/content/stable';
import * as eggs from '../../website/common/script/content/eggs';
import * as potions from '../../website/common/script/content/hatching-potions';
import stable from '../../website/common/script/content/stable';
import eggs from '../../website/common/script/content/eggs';
import potions from '../../website/common/script/content/hatching-potions';
describe('stable', () => {
describe('dropPets', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(new Date('2020-05-20'));
});
afterEach(() => {
clock.restore();
});
it('contains a pet for each drop potion * each drop egg', () => {
const numberOfDropPotions = Object.keys(potions.drops).length;
const numberOfDropEggs = Object.keys(eggs.drops).length;
+94 -12
View File
@@ -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-01T09:00:00.000Z');
});
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;
});
});
});
+11
View File
@@ -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(),
@@ -59,7 +60,17 @@ export function generateReq (options = {}) {
header (header) {
return this.headers[header];
},
listeners: {},
session: {},
on (key, func) {
if (!this.listeners[key]) {
this.listeners[key] = [];
}
this.listeners[key].push(func);
},
end () {
this.listeners.close.forEach(func => func());
},
};
const req = defaultsDeep(options, defaultReq);
+6 -6
View File
@@ -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);
}
+363 -66
View File
@@ -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",
+11 -5
View File
@@ -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"
}
}
+9 -2
View File
@@ -27,8 +27,15 @@
</head>
<body>
<div id="loading-screen">
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.62 28.86c-.32-.706-1.057-1.048-1.538-.706-.48.341-1.147.393-1.78.24-.633-.153-.753-1.604-.616-3.278.136-1.673.363-2.318.506-2.925.162-.61.877-.562.962-.084.086.479.582.479 1.307-.391.724-.87.617-3.409-.218-5.474-.836-2.065.326-1.865.664-1.66.337.205.544-.102.462-1.28-.082-1.178-1.166-2.098-2.039-2.663-.873-.564-1.936-1.186-1.911-2.697.025-1.511 2.08-1.464 2.358-1.439.279.025.815-.093.506-1.663-.31-1.57-1.43-1.869-2.133-1.826-.703.042-1.177.428-2.17.053-.995-.376-1.655-.23-2.58-.023-.926.206-2.138.776-3.646 1.183-.795.219-1.064.274-1.93.288-.532.008-.755.653-.043 1.444.563.643 1.839.814 2.606.707.494-.07.608.258.563.74a8.013 8.013 0 0 0-.01 1.795c.08.6.18 1.62-.103 2.286-.14.326-.545.677-.98.653-.565-.034-1.022-.7-1.414-1.49-.825-1.662-1.793-2.014-5.404-3.535-3.248-1.367-5.007-3.5-6.096-4.874-.969-1.217-1.939-.756-1.85.342.07.852.592 3.604 1.912 5.257 1.623 2.525 4.128 3.67 7.013 3.895.755.06 1.226.208 1.29.553.095.735-.622 1.244-1.959 1.09-1.336-.157-1.907.087-1.641.848.85 1.79 2.809 1.869 3.623 1.942.275.05 1.246 0 1.764.143.605.166.735 1.005-.14 1.459-1.558.76-2.237 1.391-3.025 2.83-.595 1.13-1.108 3.022-.574 5.745.513 2.648-3.337 2.733-5 2.357-.716-.151-1.47-1.512.287-2.65 1.421-.922 1.708-1.49 1.645-2.657-.074-1.36-.824-1.458-.822-2.64v-2.82a.435.435 0 0 0-.435-.435H7.698a.435.435 0 0 1-.435-.434v-1.7a.435.435 0 0 0-.435-.435H5.501a.435.435 0 0 1-.435-.435v-1.524a.435.435 0 0 0-.435-.435H3.015a.435.435 0 0 1-.435-.435v-1.603a.435.435 0 0 0-.435-.434H.435a.435.435 0 0 0-.435.434v1.705c0 .24.195.435.435.435h1.62c.24 0 .435.195.435.435v6.076c0 .241.195.435.435.435h1.71c.241 0 .436.196.436.435v1.988c0 .24.195.434.435.434h2.402c.734-.052.862.934.854 1.286-.016.803-.923 1.06-1.352 1.395-1.145.884-2.031 1.783-1.513 3.512l.013.036c.945 2.007 3.542 1.8 5.183 1.8h10.326c.584 0 1.184.135 1.046-.545-.136-.68-.425-1.61-1.265-1.61-.84 0-.703.467-1.524.228-.821-.238-.822-1.348.411-3.279 1.276-1.649 3.46-1.524 4.781-.358 1.32 1.166.93 3.191.653 4.354-.158.82.218 1.224.669 1.213h5.242c.806-.014.647-.556.185-1.614h.003z" fill="#fff"/>
<svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
<path
xmlns="http://www.w3.org/2000/svg"
fill-rule="evenodd"
clip-rule="evenodd"
d="M79.05 72.15c-.8-1.766-2.643-2.62-3.845-1.766-1.201.855-2.867.985-4.448.602-1.584-.385-1.885-4.01-1.543-8.195.342-4.184.909-5.795 1.267-7.314.404-1.524 2.191-1.404 2.405-.209.215 1.196 1.454 1.196 3.266-.979 1.811-2.175 1.543-8.52-.546-13.684-2.088-5.163.817-4.661 1.66-4.149.844.513 1.362-.255 1.156-3.2-.204-2.945-2.916-5.247-5.096-6.657-2.184-1.41-4.842-2.967-4.78-6.745.063-3.777 5.2-3.658 5.897-3.596.697.063 2.037-.233 1.264-4.157-.773-3.924-3.575-4.673-5.332-4.567-1.758.106-2.943 1.071-5.427.133-2.484-.938-4.136-.572-6.45-.057-2.313.515-5.343 1.94-9.112 2.959-1.989.545-2.661.683-4.828.718-1.33.02-1.885 1.633-.106 3.61 1.408 1.608 4.597 2.036 6.515 1.768 1.236-.174 1.521.645 1.407 1.85a20.023 20.023 0 0 0-.024 4.488c.198 1.5.45 4.051-.258 5.713-.35.817-1.361 1.693-2.449 1.633-1.413-.084-2.555-1.75-3.537-3.726-2.06-4.152-4.48-5.033-13.509-8.835-8.12-3.417-12.516-8.749-15.24-12.185-2.421-3.042-4.846-1.89-4.626.855.179 2.128 1.48 9.008 4.781 13.141 4.058 6.314 10.32 9.177 17.534 9.739 1.885.149 3.065.52 3.225 1.383.236 1.835-1.557 3.11-4.898 2.722-3.341-.39-4.768.22-4.103 2.121 2.123 4.477 7.021 4.672 9.058 4.857.686.122 3.114 0 4.41.355 1.51.418 1.836 2.514-.353 3.648-3.892 1.903-5.59 3.479-7.561 7.075-1.486 2.826-2.77 7.555-1.435 14.365 1.283 6.62-8.342 6.83-12.497 5.89-1.793-.377-3.675-3.778.716-6.625 3.553-2.305 4.269-3.724 4.111-6.642-.184-3.4-2.058-3.644-2.053-6.598v-7.05c0-.602-.488-1.088-1.087-1.088h-3.334a1.087 1.087 0 0 1-1.087-1.087v-4.25c0-.602-.488-1.087-1.088-1.087h-3.317a1.087 1.087 0 0 1-1.087-1.088v-3.81c0-.602-.489-1.087-1.088-1.087h-4.04a1.087 1.087 0 0 1-1.089-1.088V26.25c0-.602-.488-1.088-1.087-1.088H1.088C.485 25.161 0 25.65 0 26.25v4.26c0 .602.488 1.087 1.088 1.087h4.049c.602 0 1.087.489 1.087 1.088v15.192c0 .602.489 1.087 1.088 1.087h4.277c.602 0 1.088.489 1.088 1.088v4.968c0 .602.488 1.087 1.087 1.087h6.005c1.836-.13 2.156 2.335 2.137 3.214-.04 2.007-2.308 2.652-3.382 3.487-2.861 2.21-5.077 4.459-3.78 8.781l.032.09c2.362 5.017 8.855 4.499 12.956 4.499h25.817c1.459 0 2.959.339 2.614-1.362-.342-1.7-1.063-4.024-3.162-4.024-2.1 0-1.758 1.166-3.81.57-2.054-.597-2.057-3.371 1.027-8.198 3.19-4.122 8.652-3.81 11.952-.895 3.301 2.915 2.325 7.978 1.633 10.885-.396 2.048.545 3.06 1.67 3.032H78.58c2.015-.035 1.62-1.391.464-4.035h.008z"
fill="#fff"
>
</path>
</svg>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+24 -282
View File
@@ -27,73 +27,15 @@
</div>
</div>
</div>
<div
id="app"
:class="{
'casting-spell': castingSpell,
}"
>
<!-- <banned-account-modal /> -->
<amazon-payments-modal v-if="!isStaticPage" />
<payments-success-modal />
<sub-cancel-modal-confirm v-if="isUserLoaded" />
<sub-canceled-modal v-if="isUserLoaded" />
<bug-report-modal v-if="isUserLoaded" />
<bug-report-success-modal v-if="isUserLoaded" />
<external-link-modal />
<birthday-modal />
<snackbars />
<router-view v-if="!isUserLoggedIn || isStaticPage" />
<template v-else>
<template v-if="isUserLoaded">
<chat-banner />
<damage-paused-banner />
<gems-promo-banner />
<gift-promo-banner />
<birthday-banner />
<notifications-display />
<app-menu />
<div
class="container-fluid"
:class="{'no-margin': noMargin}"
>
<app-header />
<buyModal
:item="selectedItemToBuy || {}"
:with-pin="true"
:generic-purchase="genericPurchase(selectedItemToBuy)"
@buyPressed="customPurchase($event)"
/>
<selectMembersModal
:item="selectedSpellToBuy || {}"
:group="user.party"
@memberSelected="memberSelected($event)"
/>
<div :class="{sticky: user.preferences.stickyHeader}">
<router-view />
</div>
</div>
<app-footer v-if="!hideFooter" />
<audio
id="sound"
ref="sound"
autoplay="autoplay"
></audio>
</template>
</template>
</div>
<snackbars />
<router-view v-if="!isUserLoggedIn || isStaticPage" />
<user-main v-else />
</div>
</template>
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
#app {
display: flex;
flex-direction: column;
overflow-x: hidden;
}
#loading-screen-inapp {
#melior {
color: $white;
@@ -163,68 +105,20 @@
<script>
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
import birthdayModal from '@/components/news/birthdayModal';
import AppMenu from './components/header/menu';
import AppHeader from './components/header/index';
import ChatBanner from './components/header/banners/chatBanner';
import DamagePausedBanner from './components/header/banners/damagePaused';
import GemsPromoBanner from './components/header/banners/gemsPromo';
import GiftPromoBanner from './components/header/banners/giftPromo';
import BirthdayBanner from './components/header/banners/birthdayBanner';
import AppFooter from './components/appFooter';
import notificationsDisplay from './components/notifications';
import snackbars from './components/snackbars/notifications';
import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import BuyModal from './components/shops/buyModal.vue';
import SelectMembersModal from '@/components/selectMembersModal.vue';
import notifications from '@/mixins/notifications';
import { setup as setupPayments } from '@/libs/payments';
import amazonPaymentsModal from '@/components/payments/amazonModal';
import paymentsSuccessModal from '@/components/payments/successModal';
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
import subCanceledModal from '@/components/payments/canceledModal';
import externalLinkModal from '@/components/externalLinkModal.vue';
import spellsMixin from '@/mixins/spells';
import {
CONSTANTS,
getLocalSetting,
removeLocalSetting,
} from '@/libs/userlocalManager';
const bugReportModal = () => import(/* webpackChunkName: "bug-report-modal" */'@/components/bugReportModal');
const bugReportSuccessModal = () => import(/* webpackChunkName: "bug-report-success-modal" */'@/components/bugReportSuccessModal');
import { mapState } from '@/libs/store';
import userMain from '@/pages/user-main';
import snackbars from '@/components/snackbars/notifications';
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
export default {
name: 'App',
components: {
AppMenu,
AppHeader,
AppFooter,
birthdayModal,
ChatBanner,
DamagePausedBanner,
GemsPromoBanner,
GiftPromoBanner,
BirthdayBanner,
notificationsDisplay,
snackbars,
BuyModal,
SelectMembersModal,
amazonPaymentsModal,
paymentsSuccessModal,
subCancelModalConfirm,
subCanceledModal,
bugReportModal,
bugReportSuccessModal,
externalLinkModal,
userMain,
},
mixins: [notifications, spellsMixin],
data () {
return {
selectedItemToBuy: null,
@@ -238,71 +132,25 @@ export default {
};
},
computed: {
...mapState(['isUserLoggedIn', 'browserTimezoneUtcOffset', 'isUserLoaded', 'notificationsRemoved']),
...mapState(['isUserLoggedIn', 'isUserLoaded', 'notificationsRemoved']),
...mapState({ user: 'user.data' }),
isStaticPage () {
return this.$route.meta.requiresLogin === false;
},
castingSpell () {
return this.$store.state.spellOptions.castingSpell;
},
noMargin () {
return ['privateMessages'].includes(this.$route.name);
},
hideFooter () {
return ['privateMessages'].includes(this.$route.name);
},
},
created () {
this.$root.$on('playSound', sound => {
const theme = this.user.preferences.sound;
if (!theme || theme === 'off') {
return;
}
const file = `/static/audio/${theme}/${sound}`;
if (this.audioSuffix === null) {
this.audioSource = document.createElement('source');
if (this.$refs.sound.canPlayType('audio/ogg')) {
this.audioSuffix = '.ogg';
this.audioSource.type = 'audio/ogg';
} else {
this.audioSuffix = '.mp3';
this.audioSource.type = 'audio/mp3';
}
this.audioSource.src = file + this.audioSuffix;
this.$refs.sound.appendChild(this.audioSource);
} else {
this.audioSource.src = file + this.audioSuffix;
}
this.$refs.sound.load();
// Setup listener for title
this.$store.watch(state => state.title, title => {
document.title = title;
});
// @TODO: I'm not sure these should be at the app level.
// Can we move these back into shop/inventory or maybe they need a lateral move?
this.$root.$on('buyModal::showItem', item => {
this.selectedItemToBuy = item;
this.$root.$emit('bv::show::modal', 'buy-modal');
});
this.$root.$on('bv::modal::hidden', event => {
if (event.componentId === 'buy-modal') {
this.$root.$emit('buyModal::hidden', this.selectedItemToBuy.key);
this.$store.watch(state => state.isUserLoaded, () => {
if (this.isUserLoaded) {
this.hideLoadingScreen();
}
});
this.$root.$on('selectMembersModal::showItem', item => {
this.selectedSpellToBuy = item;
this.$root.$emit('bv::show::modal', 'select-member-modal');
});
// @TODO split up this file, it's too big
loadProgressBar({
showSpinner: false,
this.$nextTick(() => {
// Load external scripts after the app has been rendered
Analytics.load();
});
axios.interceptors.response.use(response => { // Set up Response interceptors
@@ -414,79 +262,20 @@ export default {
return Promise.reject(error);
});
// Setup listener for title
this.$store.watch(state => state.title, title => {
document.title = title;
});
this.$nextTick(() => {
// Load external scripts after the app has been rendered
Analytics.load();
});
if (this.isUserLoggedIn && !this.isStaticPage) {
// Load the user and the user tasks
Promise.all([
this.$store.dispatch('user:fetch'),
this.$store.dispatch('tasks:fetchUserTasks'),
]).then(() => {
this.$store.state.isUserLoaded = true;
Analytics.setUser();
Analytics.updateUser();
return axios.get(
'/api/v4/i18n/browser-script',
{
language: this.user.preferences.language,
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
Expires: '0',
},
},
);
}).then(() => {
const i18nData = window && window['habitica-i18n'];
this.$loadLocale(i18nData);
this.hideLoadingScreen();
// Adjust the timezone offset
const browserTimezoneOffset = -this.browserTimezoneUtcOffset;
if (this.user.preferences.timezoneOffset !== browserTimezoneOffset) {
this.$store.dispatch('user:set', {
'preferences.timezoneOffset': browserTimezoneOffset,
});
}
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
if (appState) {
appState = JSON.parse(appState);
if (appState.paymentCompleted) {
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
this.$root.$emit('habitica:payment-success', appState);
}
}
this.$nextTick(() => {
// Load external scripts after the app has been rendered
setupPayments();
});
}).catch(err => {
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
});
} else {
this.hideLoadingScreen();
}
},
beforeDestroy () {
this.$root.$off('playSound');
this.$root.$off('buyModal::showItem');
this.$root.$off('selectMembersModal::showItem');
},
mounted () {
// Remove the index.html loading screen and now show the inapp loading
const loadingScreen = document.getElementById('loading-screen');
if (loadingScreen) document.body.removeChild(loadingScreen);
if (this.isStaticPage || !this.isUserLoggedIn) {
this.hideLoadingScreen();
}
},
methods: {
hideLoadingScreen () {
this.loading = false;
},
checkForBannedUser (error) {
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
const parseSettings = JSON.parse(AUTH_SETTINGS);
@@ -507,57 +296,10 @@ export default {
this.$store.dispatch('auth:logout', { redirectToLogin: true });
return true;
},
itemSelected (item) {
this.selectedItemToBuy = item;
},
genericPurchase (item) {
if (!item) return false;
if (['card', 'debuffPotion'].includes(item.purchaseType)) return false;
return true;
},
customPurchase (item) {
if (item.purchaseType === 'card') {
this.selectedSpellToBuy = item;
// hide the dialog
this.$root.$emit('bv::hide::modal', 'buy-modal');
// remove the dialog from our modal-stack,
// the default hidden event is delayed
this.$root.$emit('bv::modal::hidden', {
target: {
id: 'buy-modal',
},
});
this.$root.$emit('bv::show::modal', 'select-member-modal');
}
if (item.purchaseType === 'debuffPotion') {
this.castStart(item, this.user);
}
},
async memberSelected (member) {
await this.castStart(this.selectedSpellToBuy, member);
this.selectedSpellToBuy = null;
if (this.user.party._id) {
this.$store.dispatch('party:getMembers', { forceLoad: true });
}
this.$root.$emit('bv::hide::modal', 'select-member-modal');
},
hideLoadingScreen () {
this.loading = false;
},
},
};
</script>
<style src="intro.js/minified/introjs.min.css"></style>
<style src="axios-progress-bar/dist/nprogress.css"></style>
<style src="@/assets/scss/index.scss" lang="scss"></style>
<style src="@/assets/scss/sprites.scss" lang="scss"></style>
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
File diff suppressed because it is too large Load Diff
+26 -5
View File
@@ -174,6 +174,30 @@
}
}
.btn-warning {
background: $orange-10;
color: $white !important;
&:hover:not(:disabled):not(.disabled) {
background: $orange-100;
color: $white;
}
&:focus {
background: $orange-10;
border-color: $purple-400;
}
&:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus {
box-shadow: none;
border-color: $purple-400;
}
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
background: $orange-10;
}
}
.btn-success {
background: $green-50;
border: 1px solid transparent;
@@ -217,16 +241,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;

Some files were not shown because too many files have changed in this diff Show More