Compare commits

..

393 Commits

Author SHA1 Message Date
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
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 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 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 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
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
Sabe Jones dde675fdc1 5.25.1 2024-06-11 13:22:43 -05:00
Weblate d34502bba2 Merge branch 'origin/develop' into Weblate. 2024-06-11 20:21:52 +02:00
Weblate 309954eb44 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (German)

Currently translated at 98.4% (129 of 131 strings)

Translated using Weblate (German)

Currently translated at 91.2% (2830 of 3103 strings)

Translated using Weblate (German)

Currently translated at 61.5% (56 of 91 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 (Russian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 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 (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (German)

Currently translated at 91.1% (2828 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.5% (232 of 233 strings)

Translated using Weblate (German)

Currently translated at 91.0% (2826 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 75.5% (142 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 80.8% (152 of 188 strings)

Translated using Weblate (German)

Currently translated at 91.0% (2824 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 97.2% (3018 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 98.0% (851 of 868 strings)

Translated using Weblate (German)

Currently translated at 90.8% (2820 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Russian)

Currently translated at 99.2% (130 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Russian)

Currently translated at 95.7% (2972 of 3103 strings)

Translated using Weblate (Russian)

Currently translated at 95.7% (2972 of 3103 strings)

Translated using Weblate (German)

Currently translated at 90.6% (2814 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Spanish)

Currently translated at 67.5% (127 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (German)

Currently translated at 90.6% (2813 of 3103 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (German)

Currently translated at 90.5% (2810 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 95.9% (2976 of 3103 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (770 of 773 strings)

Translated using Weblate (Japanese)

Currently translated at 97.2% (844 of 868 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3103 of 3103 strings)

Translated using Weblate (German)

Currently translated at 90.4% (2808 of 3103 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (224 of 224 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (French)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Russian)

Currently translated at 99.2% (130 of 131 strings)

Translated using Weblate (French)

Currently translated at 99.6% (3092 of 3103 strings)

Translated using Weblate (German)

Currently translated at 90.4% (2807 of 3103 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (233 of 233 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3103 of 3103 strings)

Translated using Weblate (German)

Currently translated at 90.3% (2805 of 3103 strings)

Translated using Weblate (German)

Currently translated at 98.9% (186 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 94.5% (2934 of 3103 strings)

Translated using Weblate (Russian)

Currently translated at 32.4% (61 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 96.7% (748 of 773 strings)

Translated using Weblate (Russian)

Currently translated at 94.7% (822 of 868 strings)

Translated using Weblate (Russian)

Currently translated at 94.7% (822 of 868 strings)

Translated using Weblate (German)

Currently translated at 98.4% (185 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 95.8% (2975 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% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (German)

Currently translated at 98.7% (230 of 233 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Russian)

Currently translated at 99.2% (130 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Russian)

Currently translated at 95.4% (273 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Russian)

Currently translated at 96.0% (410 of 427 strings)

Translated using Weblate (Russian)

Currently translated at 96.0% (410 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Russian)

Currently translated at 94.5% (2933 of 3103 strings)

Translated using Weblate (German)

Currently translated at 90.2% (2802 of 3103 strings)

Translated using Weblate (Russian)

Currently translated at 29.7% (56 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 29.7% (56 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 29.7% (56 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 96.6% (747 of 773 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 98.8% (256 of 259 strings)

Translated using Weblate (Russian)

Currently translated at 24.4% (46 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 24.4% (46 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 96.4% (161 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 96.4% (161 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 79.5% (206 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Russian)

Currently translated at 23.4% (44 of 188 strings)

Translated using Weblate (Russian)

Currently translated at 23.4% (44 of 188 strings)

Co-authored-by: Alberto jor avondet <albertoavondet@gmail.com>
Co-authored-by: Daniel Baez <lajbelms@gmail.com>
Co-authored-by: Egor Pakhomov <yaestgrut75@gmail.com>
Co-authored-by: Felix Zelinsky <jockstrap_either179@sl.felky.de>
Co-authored-by: Inferno <mishaad051@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Mencius <beautyalinap@gmail.com>
Co-authored-by: Miroslav <entferner@yandex.com>
Co-authored-by: Nikita Maximov <ruvemaximus@gmail.com>
Co-authored-by: Romane <luoma.nei72@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: Weblate <noreply@weblate.org>
Co-authored-by: nate <njarisch@outlook.com>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: Євген Хеддок <jevhed@proton.me>
Co-authored-by: Юрий Артамонов <zilberstein2211@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
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/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/uk/
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/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
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/pets/uk/
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/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/
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/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-06-11 20:21:37 +02:00
Phillip Thelen 98f9d2a8f4 Update to new method for fcm (#15238)
* begin moving to new fcm library

* Add error handling

* Add opening notification to correct screen

* Fix tests and make async

* lint fix

* Rename pushNotificationstest..js to pushNotifications.test.js

* fix(potions): remove Fungi Potion time banner

* 5.24.3

* update(content): add 2024-06 content prebuild (#15231)

* update sprites

* add 2024-06 content

* add 2024-06 enchanted armoire items

* update sprites

* update sprites

* fix errors found in testing

* Fix liveliness probes being rate limited (#15236)

* Do not rate limit any liveliness probes

* update example config

* Translated using Weblate (German)

Currently translated at 96.2% (181 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (769 of 773 strings)

Translated using Weblate (German)

Currently translated at 93.6% (176 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 96.2% (2972 of 3089 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (232 of 232 strings)

Translated using Weblate (Japanese)

Currently translated at 96.8% (841 of 868 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 (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (German)

Currently translated at 86.7% (163 of 188 strings)

Translated using Weblate (German)

Currently translated at 85.1% (160 of 188 strings)

Translated using Weblate (German)

Currently translated at 84.0% (158 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (German)

Currently translated at 83.5% (157 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 82.9% (156 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 81.9% (154 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 79.2% (149 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 90.6% (2799 of 3089 strings)

Translated using Weblate (German)

Currently translated at 77.6% (146 of 188 strings)

Translated using Weblate (German)

Currently translated at 90.5% (2797 of 3089 strings)

Translated using Weblate (German)

Currently translated at 90.4% (2794 of 3089 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (German)

Currently translated at 90.1% (2786 of 3089 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 77.1% (145 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (763 of 773 strings)

Translated using Weblate (Chinese (Simplified))

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 (Spanish)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (German)

Currently translated at 90.0% (2782 of 3089 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 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 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 75.0% (141 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 99.0% (766 of 773 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Japanese)

Currently translated at 98.8% (764 of 773 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (258 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 62.5% (1931 of 3089 strings)

Translated using Weblate (German)

Currently translated at 89.8% (2777 of 3089 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 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 (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (French)

Currently translated at 82.9% (156 of 188 strings)

Translated using Weblate (German)

Currently translated at 93.0% (241 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (427 of 427 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 (Japanese)

Currently translated at 99.2% (257 of 259 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 (German)

Currently translated at 92.2% (239 of 259 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 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 91.8% (238 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.5% (762 of 773 strings)

Translated using Weblate (German)

Currently translated at 90.3% (234 of 259 strings)

Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kem Kembo <medamamef@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: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
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/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
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/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/uk/
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/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks

* 5.25.0

* Fix dockerfile (#15241)

* Fix issue with l4p not resetting properly (#15240)

* actually clear out seeking field on user. Even when creating a party

* Add tests to ensure party.seeking is cleared

* fix(lint): don't assign unused const

---------

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

---------

Co-authored-by: Sabe Jones <sabe@habitica.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kem Kembo <medamamef@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: Rafał Jagielski <jagielski.rafal.uwm@gmail.com>
2024-06-11 13:19:03 -05:00
Sabe Jones 5719e5e996 fix(chat): validate group membership, by @phillipthelen 2024-06-11 13:14:47 -05: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 758b6138c2 Fix issue with l4p not resetting properly (#15240)
* actually clear out seeking field on user. Even when creating a party

* Add tests to ensure party.seeking is cleared

* fix(lint): don't assign unused const

---------

Co-authored-by: Sabe Jones <sabe@habitica.com>
2024-06-10 17:00:31 -05:00
Rafał Jagielski 37f08c4534 Fix dockerfile (#15241) 2024-06-10 16:41:26 -05: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 3cd966bc03 5.25.0 2024-05-29 11:39:42 -05:00
Sabe Jones 28531f3e2a Merge branch 'develop' into release 2024-05-29 11:39:34 -05:00
Weblate 7ecaf098cd Merge branch 'origin/develop' into Weblate. 2024-05-29 18:36:31 +02:00
Weblate ef1912d571 Translated using Weblate (German)
Currently translated at 96.2% (181 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (769 of 773 strings)

Translated using Weblate (German)

Currently translated at 93.6% (176 of 188 strings)

Translated using Weblate (Japanese)

Currently translated at 96.2% (2972 of 3089 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (232 of 232 strings)

Translated using Weblate (Japanese)

Currently translated at 96.8% (841 of 868 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 (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (German)

Currently translated at 86.7% (163 of 188 strings)

Translated using Weblate (German)

Currently translated at 85.1% (160 of 188 strings)

Translated using Weblate (German)

Currently translated at 84.0% (158 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (German)

Currently translated at 83.5% (157 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 82.9% (156 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 81.9% (154 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 79.2% (149 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 90.6% (2799 of 3089 strings)

Translated using Weblate (German)

Currently translated at 77.6% (146 of 188 strings)

Translated using Weblate (German)

Currently translated at 90.5% (2797 of 3089 strings)

Translated using Weblate (German)

Currently translated at 90.4% (2794 of 3089 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (German)

Currently translated at 90.1% (2786 of 3089 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 77.1% (145 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (763 of 773 strings)

Translated using Weblate (Chinese (Simplified))

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 (Spanish)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (German)

Currently translated at 90.0% (2782 of 3089 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 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 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 75.0% (141 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 99.0% (766 of 773 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Japanese)

Currently translated at 98.8% (764 of 773 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (258 of 259 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (378 of 378 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 62.5% (1931 of 3089 strings)

Translated using Weblate (German)

Currently translated at 89.8% (2777 of 3089 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 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 (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (French)

Currently translated at 82.9% (156 of 188 strings)

Translated using Weblate (German)

Currently translated at 93.0% (241 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (286 of 286 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (427 of 427 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 (Japanese)

Currently translated at 99.2% (257 of 259 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 (German)

Currently translated at 92.2% (239 of 259 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 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 91.8% (238 of 259 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.5% (762 of 773 strings)

Translated using Weblate (German)

Currently translated at 90.3% (234 of 259 strings)

Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kem Kembo <medamamef@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: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/content/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
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/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
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/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/uk/
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/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-05-29 18:36:22 +02:00
Phillip Thelen cd685c1b2b Fix liveliness probes being rate limited (#15236)
* Do not rate limit any liveliness probes

* update example config
2024-05-29 11:34:40 -05: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
Natalie b2dde8a977 update(content): add 2024-06 content prebuild (#15231)
* update sprites

* add 2024-06 content

* add 2024-06 enchanted armoire items

* update sprites

* update sprites

* fix errors found in testing
2024-05-28 15:23:17 -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
Sabe Jones c709adeaec 5.24.3 2024-05-17 10:50:01 -05:00
Sabe Jones c5dcd089a6 fix(potions): remove Fungi Potion time banner 2024-05-17 10:49:55 -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
Sabe Jones 934b85d716 5.24.2 2024-05-09 09:27:28 -05:00
Sabe Jones c6df34a7fc chore(subproj): update habitica-images 2024-05-09 09:27:24 -05:00
Sabe Jones c51c90ba41 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
2024-05-09 09:26:49 -05:00
Weblate d3f420144c 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
2024-05-09 16:22:33 +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
Natalie 1567f1c283 remove dempendabot.yml (#15193) 2024-05-07 16:21:06 -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
Sabe Jones 3e19b8aa96 5.24.1 2024-04-26 15:26:23 -05:00
Sabe Jones d1bc1ab05a Merge branch 'develop' into release 2024-04-26 15:25:51 -05:00
Weblate 13149d4acf Merge branch 'origin/develop' into Weblate. 2024-04-26 22:24:45 +02:00
Sabe Jones 2d4ee636ae 5.24.0 2024-04-26 15:22:08 -05:00
Phillip Thelen 42964c91f3 Fix issue with gift sub processing (#15184)
* Fix issue with gift sub processing

* Update cron.js
2024-04-26 15:15:18 -05:00
Natalie de62207504 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>
2024-04-26 15:14:45 -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
Weblate 4b796fae5d 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
2024-04-26 11:41:11 +02:00
Yeah Jack 2e9573ef92 Update README.md for better grammar (#15103) 2024-04-25 15:25:33 -04:00
dependabot[bot] 384bfce3eb 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>
2024-04-25 14:34:51 -04:00
dependabot[bot] 5a8c7fb924 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>
2024-04-25 14:34:24 -04:00
dependabot[bot] 246775256e 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>
2024-04-25 14:34:01 -04:00
dependabot[bot] fa4cd8dd5a 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>
2024-04-25 14:32:59 -04:00
dependabot[bot] 5224e063f7 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>
2024-04-25 14:32:36 -04:00
dependabot[bot] e5e8b9a7ec 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>
2024-04-25 14:19:21 -04:00
dependabot[bot] 7cd76c50eb 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>
2024-04-25 14:18:07 -04:00
dependabot[bot] b520202544 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>
2024-04-25 14:17:17 -04:00
dependabot[bot] bbae882eda 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>
2024-04-25 14:12:46 -04: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
Sabe Jones ee93c8bec5 5.23.0 2024-04-08 15:49:20 -05:00
Sabe Jones c65e93e514 chore(git): update subproject 2024-04-08 15:49:17 -05:00
Sabe Jones 0fd808727c 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-04-08 15:48:32 -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
Sabe Jones 76d7f02fe8 5.22.3 2024-04-02 15:20:17 -05:00
Sabe Jones 5a9f2c610a Revert "fix(content): adjust release dates"
This reverts commit 0b230b0a87.
2024-04-02 15:20:04 -05:00
Sabe Jones 8a88d165e6 fix(event): end AF earlier 2024-04-02 11:28:07 -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 ed790c1c4d 5.22.2 2024-04-01 15:50:13 -05:00
Sabe Jones 0b230b0a87 fix(content): adjust release dates 2024-04-01 15:48:43 -05:00
Sabe Jones 75a88ab25a WIP(shops): style fixes 2024-03-29 16:14:14 -05:00
Sabe Jones 9b72221482 5.22.1 2024-03-28 17:17:32 -05:00
Sabe Jones ef1f27c09f Squashed commit of the following:
commit 0eb12305a58c148027759ea257f0f287796afdda
Author: Sabe Jones <sabe@habitica.com>
Date:   Thu Mar 28 17:13:05 2024 -0500

    feat(event): start April 1 midnight end April 2 8pm

commit de75cf2b9616ad4fe56051d5644c556bd40b874e
Author: Sabe Jones <sabe@habitica.com>
Date:   Thu Mar 28 08:52:01 2024 -0500

    fix(event): correct start timing

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

    feat(event): AF by @CuriousMagpie
2024-03-28 17:17:17 -05:00
Sabe Jones c762146ec9 fix(quests): egg availability, again 2024-03-26 13:09:41 -05:00
Sabe Jones 39fc6248d6 fix(event): adjust Egg quest release date 2024-03-26 06:48:45 -05:00
Sabe Jones ff0bb9d005 5.22.0 2024-03-21 15:32:41 -05:00
Sabe Jones 6425afd58d Merge branch 'develop' into release 2024-03-21 15:32:36 -05:00
Weblate c8498bd4e3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 63.5% (1930 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% (137 of 137 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (764 of 764 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 (Ukrainian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (282 of 282 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% (3035 of 3035 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Ukrainian)

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 (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Russian)

Currently translated at 29.1% (40 of 137 strings)

Translated using Weblate (Russian)

Currently translated at 29.1% (40 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (15 of 15 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% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (German)

Currently translated at 96.9% (828 of 854 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Japanese)

Currently translated at 98.4% (841 of 854 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Japanese)

Currently translated at 98.1% (161 of 164 strings)

Translated using Weblate (Korean)

Currently translated at 3.6% (5 of 137 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% (282 of 282 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% (427 of 427 strings)

Translated using Weblate (Chinese (Simplified))

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 (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 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% (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% (60 of 60 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (282 of 282 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 100.0% (110 of 110 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% (56 of 56 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% (427 of 427 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 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% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Spanish)

Currently translated at 91.2% (125 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (418 of 427 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Spanish)

Currently translated at 86.1% (118 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (Japanese)

Currently translated at 97.6% (834 of 854 strings)

Translated using Weblate (Japanese)

Currently translated at 97.5% (160 of 164 strings)

Translated using Weblate (Korean)

Currently translated at 74.3% (122 of 164 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 82.4% (113 of 137 strings)

Translated using Weblate (Ukrainian)

Currently translated at 63.5% (1929 of 3035 strings)

Translated using Weblate (German)

Currently translated at 96.7% (739 of 764 strings)

Translated using Weblate (German)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (German)

Currently translated at 94.7% (218 of 230 strings)

Translated using Weblate (French)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (German)

Currently translated at 98.7% (236 of 239 strings)

Translated using Weblate (German)

Currently translated at 97.2% (177 of 182 strings)

Translated using Weblate (German)

Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Javiera <javipuga99@gmail.com>
Co-authored-by: Kem Kembo <medamamef@gmail.com>
Co-authored-by: Mencius <beautyalinap@gmail.com>
Co-authored-by: Nikita Maximov <ruvemaximus@gmail.com>
Co-authored-by: Polina Reshetnikova <resh4096@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Srish <ouawcloud@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: 윤성열 <existmaster@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
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/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/death/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
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/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/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/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/spells/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/
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/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-03-21 21:31:35 +01:00
Natalie 64f5c170d0 update(content): 2024-04 content prebuild (#15176)
* update(content): starting april prebuild

* update(content): april prebuild

* update(content): april prebuild
2024-03-21 15:25:33 -05:00
Sabe Jones 515b62d1ce chore(npm): update package lock 2024-03-15 14:45:24 -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 7e29c7d624 5.21.0 2024-03-13 16:36:29 -05:00
Sabe Jones 669d24fe43 Merge branch 'develop' into release 2024-03-13 16:36:22 -05:00
Weblate 439451a7e8 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (3035 of 3035 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 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% (131 of 131 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% (3035 of 3035 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Romanian)

Currently translated at 71.7% (165 of 230 strings)

Translated using Weblate (Romanian)

Currently translated at 89.3% (117 of 131 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% (183 of 183 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3035 of 3035 strings)

Translated using Weblate (Spanish)

Currently translated at 77.3% (106 of 137 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Spanish)

Currently translated at 75.1% (103 of 137 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Spanish)

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 (Spanish)

Currently translated at 100.0% (3035 of 3035 strings)

Translated using Weblate (Spanish)

Currently translated at 64.2% (88 of 137 strings)

Translated using Weblate (Spanish)

Currently translated at 64.2% (88 of 137 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Spanish)

Currently translated at 99.5% (3020 of 3035 strings)

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 89.6% (2720 of 3035 strings)

Translated using Weblate (German)

Currently translated at 97.9% (234 of 239 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (German)

Currently translated at 96.4% (737 of 764 strings)

Translated using Weblate (German)

Currently translated at 98.1% (108 of 110 strings)

Translated using Weblate (German)

Currently translated at 96.1% (821 of 854 strings)

Translated using Weblate (German)

Currently translated at 98.5% (138 of 140 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (230 of 230 strings)

Translated using Weblate (German)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.9% (3034 of 3035 strings)

Translated using Weblate (Spanish)

Currently translated at 99.2% (3013 of 3035 strings)

Translated using Weblate (German)

Currently translated at 96.6% (231 of 239 strings)

Translated using Weblate (German)

Currently translated at 98.9% (187 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (854 of 854 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (854 of 854 strings)

Translated using Weblate (French)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Spanish)

Currently translated at 98.5% (2991 of 3035 strings)

Translated using Weblate (French)

Currently translated at 100.0% (110 of 110 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% (60 of 60 strings)

Translated using Weblate (Spanish)

Currently translated at 98.1% (2979 of 3035 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% (110 of 110 strings)

Translated using Weblate (German)

Currently translated at 89.4% (2716 of 3035 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 (Russian)

Currently translated at 99.5% (229 of 230 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 95.5% (108 of 113 strings)

Translated using Weblate (German)

Currently translated at 98.4% (129 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% (427 of 427 strings)

Translated using Weblate (Chinese (Simplified))

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 (German)

Currently translated at 89.3% (2712 of 3035 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (749 of 764 strings)

Translated using Weblate (German)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (German)

Currently translated at 98.1% (107 of 109 strings)

Translated using Weblate (German)

Currently translated at 95.1% (813 of 854 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (159 of 159 strings)

Co-authored-by: AGM <yoartgm@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Delta S <deseji93@gmail.com>
Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Gabriela <gabisouzars5@gmail.com>
Co-authored-by: Ike Osenberg <ike.osenberg@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Javiera <javipuga99@gmail.com>
Co-authored-by: Nik Ermilov <edaonh@gmail.com>
Co-authored-by: Quim Martínez Lara <quimml60@gmail.com>
Co-authored-by: Raul Royo Rubio <royografico@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>
Co-authored-by: fanxiaoxi <fanxiaoxi1115@gmail.com>
Co-authored-by: Χρήστος Joia <hristosjoia@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/de/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/es/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/de/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/death/es/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/de/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/
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/ro/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/de/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/
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/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ro/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/
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/Contrib
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-03-13 22:35:45 +01:00
Natalie 3e5226de67 update(content): add 2024 Spring Fling (#15168)
* update(content): add spring fling images, items, and placeholder text

* update(dates) : set canonical dates

* update(dates/quests): set canonical dates for both, add egg quest availability

* update(date): update quest availability start date

* update(content): add magic hatching potions

* fix(dates): canonical dates

* fix(event): add correct event name to potions

* fix(dates): canonical dates

* fix(dates): fix UTC offset, remove package.json and package-lock.json

* fix(dates): canonical

* fix: re-added package.json and package-lock.json

* update(content): add gear strings

* update(content): add Rogue off-hand string, canonical dates
2024-03-13 16:34:41 -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 8c7a0b4861 5.20.0 2024-03-11 10:00:05 -05:00
Sabe Jones 8fa91a3805 chore(node): upgrade to Node 20 2024-03-11 09:59:57 -05:00
Sabe Jones 037fb6737d Merge branch 'release' into sabrecat/customizations 2024-03-04 14:25:53 -06:00
Sabe Jones c554a1a57d 5.19.1 2024-03-04 14:25:14 -06:00
Sabe Jones 9406f0fa22 Merge branch 'develop' into release 2024-03-04 14:25:10 -06:00
Weblate d081a2bdba Translated using Weblate (French)
Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (French)

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (230 of 230 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.9% (3034 of 3035 strings)

Translated using Weblate (Portuguese)

Currently translated at 58.4% (1775 of 3035 strings)

Translated using Weblate (Romanian)

Currently translated at 95.3% (228 of 239 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.2% (136 of 137 strings)

Translated using Weblate (Russian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (German)

Currently translated at 94.4% (103 of 109 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (164 of 164 strings)

Translated using Weblate (German)

Currently translated at 100.0% (164 of 164 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% (183 of 183 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 71.4% (65 of 91 strings)

Translated using Weblate (Russian)

Currently translated at 97.2% (106 of 109 strings)

Translated using Weblate (Portuguese)

Currently translated at 96.7% (826 of 854 strings)

Translated using Weblate (Russian)

Currently translated at 99.3% (160 of 161 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% (427 of 427 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 75.9% (104 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 73.7% (101 of 137 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3035 of 3035 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% (3035 of 3035 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.9% (3034 of 3035 strings)

Translated using Weblate (Russian)

Currently translated at 21.8% (30 of 137 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 91.2% (125 of 137 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (854 of 854 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (854 of 854 strings)

Translated using Weblate (French)

Currently translated at 100.0% (230 of 230 strings)

Translated using Weblate (French)

Currently translated at 99.9% (3033 of 3035 strings)

Translated using Weblate (French)

Currently translated at 99.9% (3033 of 3035 strings)

Translated using Weblate (Spanish)

Currently translated at 97.6% (2965 of 3035 strings)

Translated using Weblate (Portuguese)

Currently translated at 25.5% (35 of 137 strings)

Translated using Weblate (Portuguese)

Currently translated at 96.0% (820 of 854 strings)

Translated using Weblate (French)

Currently translated at 100.0% (854 of 854 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (183 of 183 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (377 of 377 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (847 of 847 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (229 of 229 strings)

Translated using Weblate (Spanish)

Currently translated at 98.4% (2965 of 3013 strings)

Co-authored-by: Alcatraz Huo <alrcatraz@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: Gabriela <gabisouzars5@gmail.com>
Co-authored-by: Icaro <icaro.mascarenhas@outlook.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Phyan Phoenix <kirill.farick@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>
Co-authored-by: YuyingLiang <standingfish.malina@gmail.com>
Co-authored-by: Χρήστος Joia <hristosjoia@gmail.com>
Co-authored-by: Данила Мальцев <maltsev-danila@inbox.ru>
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
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/de/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ru/
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/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/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/front/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
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/pt/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ro/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hans/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/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/Limited
Translation: Habitica/Questscontent
Translation: Habitica/Spells
Translation: Habitica/Subscriber
2024-03-04 20:03:54 +01: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 dac09a9027 chore(news): remove obsolete link 2024-02-29 15:38:03 -06:00
Sabe Jones f8e56c02f0 Squashed commit of the following:
commit d30dff2311087ff2fe5f3e2a913c594abeee6b0e
Author: Sabe Jones <sabe@habitica.com>
Date:   Tue Feb 27 16:01:11 2024 -0600

    fix(challenge): move isOfficial to mount process

commit ae52dca3cd0b4fd490f07b1979049803ce2f1e2f
Merge: 2b20ff1e46 2c6e82a58a
Author: Sabe Jones <sabe@habitica.com>
Date:   Tue Feb 27 15:20:40 2024 -0600

    Merge branch 'release' into phillip/challenges_official

commit 2b20ff1e46b1447eac3f9dbdf29566154c9fa656
Author: Sabe Jones <sabe@habitica.com>
Date:   Wed Feb 14 15:31:22 2024 -0600

    fix(tests): correct lint and TypeError

commit 5dae5c716f11db4c652e423eab43805ddfac3455
Merge: 29d9edc7aa 1a3c2f64e4
Author: Sabe Jones <sabe@habitica.com>
Date:   Wed Feb 14 15:01:18 2024 -0600

    Merge branch 'release' into phillip/challenges_official

commit 29d9edc7aa7445d24f5be24ca923719a4ab5f70d
Author: Sabe Jones <sabe@habitica.com>
Date:   Wed Feb 14 14:41:16 2024 -0600

    fix(challenges): don't momentarily show Report on official

commit f994d12775107cba7ec816ee522cfeb0c69ef567
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Feb 13 10:08:08 2024 +0100

    hide report button for official challenges

commit ac06dcaca701b91913d5fc5307626b1616a9e0e8
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Feb 13 10:04:49 2024 +0100

    prevent official challenges from being flagged

commit a07ce1e6de66a2c833c6f392cf396a7743ca0f97
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Feb 5 19:12:17 2024 +0100

    test shouldn’t be exclusive

commit 4c2436a1a0fa905530b7e1cd66f75a2b07be5810
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Feb 5 19:11:20 2024 +0100

    remove log

commit 292f3a578d51fd08c572afc574cc73d08356f46a
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Feb 5 19:10:13 2024 +0100

    Automatically set official field on challenges if habitica_official cateogory is set
2024-02-28 14:31:05 -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
780 changed files with 57129 additions and 41548 deletions
+1
View File
@@ -5,6 +5,7 @@ module.exports = {
'habitrpg/lib/node',
],
rules: {
'prefer-regex-literals': 'warn',
'import/no-extraneous-dependencies': 'off',
},
};
-150
View File
@@ -1,150 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
time: "06:00"
timezone: Europe/Rome
open-pull-requests-limit: 99
ignore:
- dependency-name: express-validator
versions:
- 6.10.0
- 6.10.1
- 6.9.2
- dependency-name: "@babel/core"
versions:
- 7.12.13
- 7.12.16
- 7.12.17
- 7.13.1
- 7.13.10
- 7.13.13
- 7.13.14
- 7.13.15
- 7.13.8
- dependency-name: redis
versions:
- 3.1.0
- dependency-name: stripe
versions:
- 8.134.0
- 8.135.0
- 8.137.0
- 8.138.0
- 8.140.0
- 8.142.0
- dependency-name: "@babel/register"
versions:
- 7.12.13
- 7.13.14
- 7.13.8
- dependency-name: mongoose
versions:
- 5.11.14
- 5.11.15
- 5.11.16
- 5.11.17
- 5.11.18
- 5.11.19
- 5.12.0
- 5.12.1
- 5.12.2
- 5.12.3
- dependency-name: jwks-rsa
versions:
- 1.12.3
- 2.0.1
- 2.0.2
- dependency-name: "@babel/preset-env"
versions:
- 7.12.13
- 7.12.16
- 7.12.17
- 7.13.10
- 7.13.12
- 7.13.8
- 7.13.9
- dependency-name: image-size
versions:
- 0.9.4
- 0.9.5
- 0.9.7
- dependency-name: winston-loggly-bulk
versions:
- 3.2.0
- dependency-name: chai
versions:
- 4.3.0
- 4.3.3
- dependency-name: mocha
versions:
- 8.2.1
- 8.3.0
- 8.3.1
- dependency-name: "@google-cloud/trace-agent"
versions:
- 5.1.2
- dependency-name: monk
versions:
- 7.3.3
- package-ecosystem: npm
directory: "/website/client"
schedule:
interval: weekly
time: "06:00"
timezone: Europe/Rome
open-pull-requests-limit: 99
ignore:
- dependency-name: eslint-plugin-vue
versions:
- 7.5.0
- 7.6.0
- 7.7.0
- 7.8.0
- 7.9.0
- dependency-name: core-js
versions:
- 3.10.0
- 3.10.1
- 3.9.0
- 3.9.1
- dependency-name: bootstrap
versions:
- 4.6.0
- dependency-name: y18n
versions:
- 4.0.1
- dependency-name: hellojs
versions:
- 1.18.8
- 1.19.2
- dependency-name: chai
versions:
- 4.3.0
- 4.3.3
- dependency-name: amplitude-js
versions:
- 7.4.2
- 7.4.3
- 7.4.4
- dependency-name: pug
versions:
- 3.0.2
- dependency-name: sass
versions:
- 1.32.6
- 1.32.7
- 1.32.8
- dependency-name: "@vue/test-utils"
versions:
- 1.1.2
- 1.1.3
- dependency-name: intro.js
versions:
- 3.2.1
- 3.3.1
- dependency-name: sass-loader
versions:
- 10.1.1
+49 -39
View File
@@ -10,19 +10,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -31,19 +32,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -52,19 +54,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -74,19 +77,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -95,19 +99,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -117,14 +122,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
mongodb-version: [4.2]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -132,10 +137,11 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -146,14 +152,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
mongodb-version: [4.2]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -161,10 +167,11 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -175,14 +182,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
mongodb-version: [4.2]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -190,10 +197,11 @@ jobs:
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -205,19 +213,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
npm i
env:
CI: true
NODE_ENV: test
@@ -228,15 +237,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
node-version: [21.x]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: sudo apt-get -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
+1 -1
View File
@@ -1 +1 @@
14
20
+6 -5
View File
@@ -1,14 +1,15 @@
FROM node:14
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", "./"]
RUN npm install
# 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
+1 -1
View File
@@ -1,7 +1,7 @@
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](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, 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 money 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:
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
+6 -1
View File
@@ -32,6 +32,7 @@
"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",
@@ -84,8 +85,12 @@
"BLOCKED_IPS": "",
"LOG_AMPLITUDE_EVENTS": "false",
"RATE_LIMITER_ENABLED": "false",
"LIVELINESS_PROBE_KEY": "",
"REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PORT": "1234",
"REDIS_PASSWORD": "12345678",
"TRUSTED_DOMAINS": "localhost,habitica.com"
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"TIME_TRAVEL_ENABLED": "false",
"DEBUG_ENABLED": "false",
"CONTENT_SWITCHOVER_TIME_OFFSET": 8
}
+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,99 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '202405_pet_group_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['LionCub-Zombie'] > 0
&& pets['LionCub-Skeleton'] > 0
&& pets['LionCub-Base'] > 0
&& pets['LionCub-Desert'] > 0
&& pets['LionCub-Red'] > 0
&& pets['LionCub-Shade'] > 0
&& pets['LionCub-White']> 0
&& pets['LionCub-Golden'] > 0
&& pets['LionCub-CottonCandyBlue'] > 0
&& pets['LionCub-CottonCandyPink'] > 0
&& pets['TigerCub-Zombie'] > 0
&& pets['TigerCub-Skeleton'] > 0
&& pets['TigerCub-Base'] > 0
&& pets['TigerCub-Desert'] > 0
&& pets['TigerCub-Red'] > 0
&& pets['TigerCub-Shade'] > 0
&& pets['TigerCub-White'] > 0
&& pets['TigerCub-Golden'] > 0
&& pets['TigerCub-CottonCandyBlue'] > 0
&& pets['TigerCub-CottonCandyPink'] > 0
&& pets['Sabretooth-Zombie'] > 0
&& pets['Sabretooth-Skeleton'] > 0
&& pets['Sabretooth-Base'] > 0
&& pets['Sabretooth-Desert'] > 0
&& pets['Sabretooth-Red'] > 0
&& pets['Sabretooth-Shade'] > 0
&& pets['Sabretooth-White'] > 0
&& pets['Sabretooth-Golden'] > 0
&& pets['Sabretooth-CottonCandyBlue'] > 0
&& pets['Sabretooth-CottonCandyPink'] > 0
&& pets['Cheetah-Zombie'] > 0
&& pets['Cheetah-Skeleton'] > 0
&& pets['Cheetah-Base'] > 0
&& pets['Cheetah-Desert'] > 0
&& pets['Cheetah-Red'] > 0
&& pets['Cheetah-Shade'] > 0
&& pets['Cheetah-White'] > 0
&& pets['Cheetah-Golden'] > 0
&& pets['Cheetah-CottonCandyBlue'] > 0
&& pets['Cheetah-CottonCandyPink'] > 0 ) {
set['achievements.cats'] = true;
}
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.updateOne({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2024-03-01') },
};
const fields = {
_id: 1,
items: 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)
.lean()
.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]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};
@@ -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
}
};
+15635 -12727
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.19.0",
"version": "5.26.1",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -27,9 +27,10 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.18.2",
"express": "^4.19.2",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
"glob": "^8.1.0",
"got": "^11.8.6",
"gulp": "^4.0.0",
@@ -66,6 +67,7 @@
"remove-markdown": "^0.5.0",
"rimraf": "^3.0.2",
"short-uuid": "^4.2.2",
"sinon": "^15.2.0",
"stripe": "^12.18.0",
"superagent": "^8.1.2",
"universal-analytics": "^0.5.3",
@@ -78,8 +80,8 @@
},
"private": true,
"engines": {
"node": "^14",
"npm": "^6"
"node": "20",
"npm": "^10"
},
"scripts": {
"lint": "eslint --ext .js --fix . && cd website/client && npm run lint",
@@ -92,11 +94,11 @@
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
"test:api-v4:integration": "gulp test:api-v4:integration",
"test:api-v4:integration:separate-server": "NODE_ENV=test gulp test:api-v4:integration:separate-server",
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
"test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
"test:common": "nyc --silent --no-clean mocha test/common --recursive",
"test:content": "nyc --silent --no-clean mocha test/content --recursive",
"test:nodemon": "gulp test:nodemon",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
"sprites": "gulp sprites:compile",
"client:dev": "cd website/client && npm run serve",
"client:build": "cd website/client && npm run build",
@@ -105,7 +107,8 @@
"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",
@@ -114,15 +117,12 @@
"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"
},
"optionalDependencies": {}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
describe('API Messages', () => {
const message = 'Only public guilds support pagination.';
+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);
});
@@ -2,7 +2,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
import * as gems from '../../../../../../website/server/libs/payments/gems';
const { i18n } = common;
@@ -4,7 +4,7 @@ import nconf from 'nconf';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
import * as gems from '../../../../../../website/server/libs/payments/gems';
const BASE_URL = nconf.get('BASE_URL');
@@ -1,4 +1,4 @@
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
import common from '../../../../../../website/common';
import {
getOneTimePaymentInfo,
-184
View File
@@ -1,184 +0,0 @@
import apn from '@parse/node-apn/mock';
import _ from 'lodash';
import nconf from 'nconf';
import gcmLib from 'node-gcm'; // works with FCM notifications too
import { model as User } from '../../../../website/server/models/user';
import {
sendNotification as sendPushNotification,
MAX_MESSAGE_LENGTH,
} from '../../../../website/server/libs/pushNotifications';
describe('pushNotifications', () => {
let user;
let fcmSendSpy;
let apnSendSpy;
const identifier = 'identifier';
const title = 'title';
const message = 'message';
beforeEach(() => {
user = new User();
fcmSendSpy = sinon.spy();
apnSendSpy = sinon.spy();
sandbox.stub(nconf, 'get').returns('true-key');
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
sandbox.stub(apn.Provider.prototype, 'send').returns({
on: () => null,
send: apnSendSpy,
});
});
afterEach(() => {
sandbox.restore();
});
it('throws if user is not supplied', () => {
expect(sendPushNotification).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if user.preferences.pushNotifications.unsubscribeFromAll is true', () => {
user.preferences.pushNotifications.unsubscribeFromAll = true;
expect(() => sendPushNotification(user)).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.identifier is not supplied', () => {
expect(() => sendPushNotification(user, {
title,
message,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.title is not supplied', () => {
expect(() => sendPushNotification(user, {
identifier,
message,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.message is not supplied', () => {
expect(() => sendPushNotification(user, {
identifier,
title,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('returns if no device is registered', () => {
sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('cuts the message to 300 chars', () => {
const longMessage = `12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
const details = {
identifier,
title,
message: longMessage,
payload: {
message: longMessage,
},
};
sendPushNotification(user, details);
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.payload.message)
.to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
expect(details.payload.message.length).to.equal(MAX_MESSAGE_LENGTH);
});
it('cuts the message to 300 chars (no payload)', () => {
const longMessage = `12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
const details = {
identifier,
title,
message: longMessage,
};
sendPushNotification(user, details);
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
});
// TODO disabled because APN relies on a Promise
xit('uses APN for iOS devices', () => {
user.pushDevices.push({
type: 'ios',
regId: '123',
});
const details = {
identifier,
title,
message,
category: 'fun',
payload: {
a: true,
b: true,
},
};
const expectedNotification = new apn.Notification({
alert: message,
sound: 'default',
category: 'fun',
payload: {
identifier,
a: true,
b: true,
},
});
sendPushNotification(user, details);
expect(apnSendSpy).to.have.been.calledOnce;
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
expect(fcmSendSpy).to.not.have.been.called;
});
});
@@ -0,0 +1,354 @@
import apn from '@parse/node-apn';
import _ from 'lodash';
import nconf from 'nconf';
import admin from 'firebase-admin';
import { model as User } from '../../../../website/server/models/user';
import {
MAX_MESSAGE_LENGTH,
} from '../../../../website/server/libs/pushNotifications';
let sendPushNotification;
describe('pushNotifications', () => {
let user;
let fcmSendSpy;
let apnSendSpy;
let updateStub;
let classStubbedInstance;
const identifier = 'identifier';
const title = 'title';
const message = 'message';
beforeEach(() => {
user = new User();
fcmSendSpy = sinon.stub().returns(Promise.resolve('success'));
apnSendSpy = sinon.stub().returns(Promise.resolve());
nconf.set('PUSH_CONFIGS_APN_ENABLED', 'true');
classStubbedInstance = sandbox.createStubInstance(apn.Provider, {
send: apnSendSpy,
});
sandbox.stub(apn, 'Provider').returns(classStubbedInstance);
delete require.cache[require.resolve('../../../../website/server/libs/pushNotifications')];
// eslint-disable-next-line global-require
sendPushNotification = require('../../../../website/server/libs/pushNotifications').sendNotification;
updateStub = sandbox.stub(User, 'updateOne').resolves();
sandbox.stub(admin, 'messaging').get(() => () => ({ send: fcmSendSpy }));
});
afterEach(() => {
sandbox.restore();
});
describe('validates supplied data', () => {
it('throws if user is not supplied', () => {
expect(sendPushNotification).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if user.preferences.pushNotifications.unsubscribeFromAll is true', () => {
user.preferences.pushNotifications.unsubscribeFromAll = true;
expect(() => sendPushNotification(user)).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.identifier is not supplied', () => {
expect(() => sendPushNotification(user, {
title,
message,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.title is not supplied', () => {
expect(() => sendPushNotification(user, {
identifier,
message,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('throws if details.message is not supplied', () => {
expect(() => sendPushNotification(user, {
identifier,
title,
})).to.throw;
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
it('returns if no device is registered', () => {
sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.not.have.been.called;
});
});
it('cuts the message to 300 chars', () => {
const longMessage = `12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
const details = {
identifier,
title,
message: longMessage,
payload: {
message: longMessage,
},
};
sendPushNotification(user, details);
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.payload.message)
.to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
expect(details.payload.message.length).to.equal(MAX_MESSAGE_LENGTH);
});
it('cuts the message to 300 chars (no payload)', () => {
const longMessage = `12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
const details = {
identifier,
title,
message: longMessage,
};
sendPushNotification(user, details);
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
});
describe('sends notifications', () => {
let details;
beforeEach(() => {
details = {
identifier,
title,
message,
category: 'fun',
payload: {
a: true,
b: true,
},
};
});
it('uses APN for iOS devices', async () => {
user.pushDevices.push({
type: 'ios',
regId: '123',
});
const expectedNotification = new apn.Notification({
alert: {
title,
body: message,
},
sound: 'default',
category: 'fun',
payload: {
identifier,
a: true,
b: true,
},
});
await sendPushNotification(user, details);
expect(apnSendSpy).to.have.been.calledOnce;
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
expect(fcmSendSpy).to.not.have.been.called;
});
it('uses FCM for Android devices', async () => {
user.pushDevices.push({
type: 'android',
regId: '123',
});
const expectedMessage = {
notification: {
title,
body: message,
},
data: {
identifier,
notificationIdentifier: identifier,
},
token: '123',
};
await sendPushNotification(user, details);
expect(fcmSendSpy).to.have.been.calledOnce;
expect(fcmSendSpy).to.have.been.calledWithMatch(expectedMessage);
expect(apnSendSpy).to.not.have.been.called;
});
it('handles multiple devices', async () => {
user.pushDevices.push({
type: 'android',
regId: '123',
});
user.pushDevices.push({
type: 'ios',
regId: '456',
});
user.pushDevices.push({
type: 'android',
regId: '789',
});
await sendPushNotification(user, details);
expect(fcmSendSpy).to.have.been.calledTwice;
expect(apnSendSpy).to.have.been.calledOnce;
});
});
describe('handles sending errors', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
it('removes unregistered fcm devices', async () => {
user.pushDevices.push({
type: 'android',
regId: '123',
});
const error = new Error();
error.code = 'messaging/registration-token-not-registered';
fcmSendSpy.rejects(error);
await sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.have.been.calledOnce;
expect(apnSendSpy).to.not.have.been.called;
await clock.tick(10);
expect(updateStub).to.have.been.calledOnce;
});
it('removes invalid fcm devices', async () => {
user.pushDevices.push({
type: 'android',
regId: '123',
});
const error = new Error();
error.code = 'messaging/registration-token-not-registered';
fcmSendSpy.rejects(error);
await sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.have.been.calledOnce;
expect(apnSendSpy).to.not.have.been.called;
expect(updateStub).to.have.been.calledOnce;
});
it('removes unregistered apn devices', async () => {
user.pushDevices.push({
type: 'ios',
regId: '123',
});
const error = {
failed: [
{
device: '123',
response: { reason: 'Unregistered' },
},
],
};
apnSendSpy.resolves(error);
await sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.have.been.calledOnce;
expect(updateStub).to.have.been.calledOnce;
});
it('removes invalid apn devices', async () => {
user.pushDevices.push({
type: 'ios',
regId: '123',
});
const error = {
failed: [
{
device: '123',
response: { reason: 'BadDeviceToken' },
},
],
};
apnSendSpy.resolves(error);
await sendPushNotification(user, {
identifier,
title,
message,
});
expect(fcmSendSpy).to.not.have.been.called;
expect(apnSendSpy).to.have.been.calledOnce;
expect(updateStub).to.have.been.calledOnce;
});
});
});
@@ -6,7 +6,7 @@ import {
} from '../../../helpers/api-unit.helper';
import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight';
import { NotAuthorized } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
describe('ensure access middlewares', () => {
let res; let req; let
@@ -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;
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ import {
generateNext,
} from '../../../helpers/api-unit.helper';
import { Forbidden } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
function checkErrorThrown (next) {
expect(next).to.have.been.calledOnce;
+62 -1
View File
@@ -7,7 +7,7 @@ import {
generateNext,
} from '../../../helpers/api-unit.helper';
import { TooManyRequests } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
import logger from '../../../../website/server/libs/logger';
describe('rateLimiter middleware', () => {
@@ -87,6 +87,67 @@ describe('rateLimiter middleware', () => {
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
});
it('does not throw when LIVELINESS_PROBE_KEY is correct', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.query.liveliness = 'abc';
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
expect(res.set).to.not.have.been.called;
});
it('limits when LIVELINESS_PROBE_KEY is incorrect', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.query.liveliness = 'das';
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 29,
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('limits when LIVELINESS_PROBE_KEY is not set', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 29,
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('throws when LIVELINESS_PROBE_KEY is blank', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.query.liveliness = '';
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 29,
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('throws when there are no available points remaining', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
@@ -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;
});
});
+15 -15
View File
@@ -1362,8 +1362,8 @@ describe('Group Model', () => {
sandbox.spy(User, 'updateMany');
});
it('formats message', () => {
const chatMessage = party.sendChat({
it('formats message', async () => {
const chatMessage = await party.sendChat({
message: 'a _new_ message with *markdown*',
user: {
_id: 'user-id',
@@ -1396,8 +1396,8 @@ describe('Group Model', () => {
expect(chat.user).to.eql('user name');
});
it('formats message as system if no user is passed in', () => {
const chat = party.sendChat({ message: 'a system message' });
it('formats message as system if no user is passed in', async () => {
const chat = await party.sendChat({ message: 'a system message' });
expect(chat.text).to.eql('a system message');
expect(validator.isUUID(chat.id)).to.eql(true);
@@ -1411,8 +1411,8 @@ describe('Group Model', () => {
expect(chat.user).to.not.exist;
});
it('updates users about new messages in party', () => {
party.sendChat({ message: 'message' });
it('updates users about new messages in party', async () => {
await party.sendChat({ message: 'message' });
expect(User.updateMany).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({
@@ -1421,12 +1421,12 @@ describe('Group Model', () => {
});
});
it('updates users about new messages in group', () => {
it('updates users about new messages in group', async () => {
const group = new Group({
type: 'guild',
});
group.sendChat({ message: 'message' });
await group.sendChat({ message: 'message' });
expect(User.updateMany).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({
@@ -1435,8 +1435,8 @@ describe('Group Model', () => {
});
});
it('does not send update to user that sent the message', () => {
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
it('does not send update to user that sent the message', async () => {
await party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
expect(User.updateMany).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({
@@ -1445,18 +1445,18 @@ describe('Group Model', () => {
});
});
it('skips sending new message notification for guilds with > 5000 members', () => {
it('skips sending new message notification for guilds with > 5000 members', async () => {
party.memberCount = 5001;
party.sendChat({ message: 'message' });
await party.sendChat({ message: 'message' });
expect(User.updateMany).to.not.be.called;
});
it('skips sending messages to the tavern', () => {
it('skips sending messages to the tavern', async () => {
party._id = TAVERN_ID;
party.sendChat({ message: 'message' });
await party.sendChat({ message: 'message' });
expect(User.updateMany).to.not.be.called;
});
@@ -2326,7 +2326,7 @@ describe('Group Model', () => {
await guild.save();
const groupMessage = guild.sendChat({ message: 'Test message.' });
const groupMessage = await guild.sendChat({ message: 'Test message.' });
await groupMessage.save();
await sleep();
+1 -1
View File
@@ -1,7 +1,7 @@
import { v4 as generateUUID } from 'uuid';
import { model as Webhook } from '../../../../website/server/models/webhook';
import { BadRequest } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
describe('Webhook Model', () => {
context('Instance Methods', () => {
@@ -7,6 +7,7 @@ import {
describe('POST /challenges/:challengeId/flag', () => {
let user;
let challengeGroup;
let challenge;
beforeEach(async () => {
@@ -20,6 +21,7 @@ describe('POST /challenges/:challengeId/flag', () => {
});
user = groupLeader;
challengeGroup = group;
challenge = await generateChallenge(user, group);
});
@@ -59,4 +61,19 @@ describe('POST /challenges/:challengeId/flag', () => {
message: t('messageChallengeFlagAlreadyReported'),
});
});
it('returns an error when user tries to flag an official challenge', async () => {
await user.updateOne({
permissions: {
challengeAdmin: true,
},
});
challenge = await generateChallenge(user, challengeGroup, { official: true });
await expect(user.post(`/challenges/${challenge._id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageChallengeFlagOfficial'),
});
});
});
@@ -331,5 +331,71 @@ describe('POST /challenges', () => {
expect(updatedChallenge.summary).to.eql(summary);
});
it('sets categories for challenges', async () => {
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'test', name: 'Test' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.categories).to.eql([testCategory]);
});
it('does not set habitica_official category for non-admins', async () => {
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
await expect(groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('noPrivAccess'),
});
});
it('sets habitica_official category for admins', async () => {
await groupLeader.updateOne({
permissions: {
challengeAdmin: true,
},
});
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.categories).to.eql([testCategory]);
});
it('sets official if the habitica_official category is set for admins', async () => {
await groupLeader.updateOne({
permissions: {
challengeAdmin: true,
},
});
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.official).to.eql(true);
});
});
});
@@ -223,4 +223,23 @@ describe('POST /chat/:chatId/flag', () => {
expect(auMessageToCheck).to.not.exist;
});
it('validates that the message belongs to the passed group', async () => {
const { group: anotherGroup, groupLeader: anotherLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'Another Guild',
type: 'guild',
privacy: 'private',
},
upgradeToGroupPlan: true,
});
const message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
await expect(anotherLeader.post(`/groups/${anotherGroup._id}/chat/${message.message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
});
@@ -1,5 +1,6 @@
import { find } from 'lodash';
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-integration/v3';
@@ -79,4 +80,35 @@ describe('POST /chat/:chatId/like', () => {
const messageToCheck = find(groupWithoutChatLikes.chat, { id: message.message.id });
expect(messageToCheck.likes[user._id]).to.equal(false);
});
it('validates that the message belongs to the passed group', async () => {
const { group: anotherGroup, groupLeader: anotherLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'Another Guild',
type: 'guild',
privacy: 'private',
},
upgradeToGroupPlan: true,
});
const message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
await expect(anotherLeader.post(`/groups/${anotherGroup._id}/chat/${message.message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('does not like a message if the user is not in the group', async () => {
const thirdUser = await generateUser();
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
await expect(thirdUser.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
});
@@ -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');
});
});
@@ -2,7 +2,7 @@ import {
generateUser,
resetHabiticaDB,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('GET /coupons/', () => {
let user;
@@ -4,7 +4,7 @@ import {
translate as t,
resetHabiticaDB,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('POST /coupons/generate/:event', () => {
let user;
@@ -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,82 @@
import nconf from 'nconf';
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
describe('POST /debug/jump-time', () => {
let user;
let today;
let nconfStub;
before(async () => {
user = await generateUser({ permissions: { fullAccess: true } });
today = new Date();
});
beforeEach(() => {
nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
});
afterEach(() => {
nconfStub.restore();
});
after(async () => {
nconf.set('TIME_TRAVEL_ENABLED', true);
await user.post('/debug/jump-time', { disable: true });
nconf.set('TIME_TRAVEL_ENABLED', false);
});
it('Jumps forward', async () => {
const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time);
expect(newResultDate.getDate()).to.eql(today.getDate() + 1);
expect(newResultDate.getMonth()).to.eql(today.getMonth());
expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
});
it('jumps back', async () => {
const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time);
expect(newResultDate.getDate()).to.eql(today.getDate() - 1);
expect(newResultDate.getMonth()).to.eql(today.getMonth());
expect(newResultDate.getFullYear()).to.eql(today.getFullYear());
});
it('can jump a lot', async () => {
const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time);
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
});
it('returns error when the user is not an admin', async () => {
const regularUser = await generateUser();
await expect(regularUser.post('/debug/jump-time', { offsetDays: 1 }))
.eventually.be.rejected.and.to.deep.equal({
code: 400,
error: 'BadRequest',
message: 'You do not have permission to time travel.',
});
});
it('returns error when not in time travel mode', async () => {
nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
await expect(user.post('/debug/jump-time', { offsetDays: 1 }))
.eventually.be.rejected.and.to.deep.equal({
code: 404,
error: 'NotFound',
message: 'Not found.',
});
});
});
@@ -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({
@@ -145,6 +145,18 @@ describe('POST /group', () => {
expect(updatedUser.party._id).to.eql(party._id);
});
it('removes seeking from user', async () => {
await user.updateOne({ 'party.seeking': new Date() });
await user.post('/groups', {
name: partyName,
type: partyType,
});
const updatedUser = await user.get('/user');
expect(updatedUser.party.seeking).to.not.exist;
});
it('does not award Party Up achievement to solo partier', async () => {
await user.post('/groups', {
name: partyName,
@@ -178,6 +178,15 @@ describe('POST /group/:groupId/join', () => {
await expect(invitedUser.get('/user')).to.eventually.not.have.nested.property('invitations.parties[0].id');
});
it('clears party.seeking from user when joining party', async () => {
await invitedUser.updateOne({ 'party.seeking': new Date() });
await invitedUser.post(`/groups/${party._id}/join`);
const updatedUser = await invitedUser.get('/user');
await expect(updatedUser.party.seeking).to.not.exist;
});
it('increments memberCount when joining party', async () => {
const oldMemberCount = party.memberCount;
@@ -4,7 +4,7 @@ import {
generateGroup,
translate as t,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('GET /heroes/party/:groupId', () => {
let user; // admin user
@@ -2,7 +2,7 @@ import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
describe('payments : paypal #checkoutSuccess', () => {
const endpoint = '/paypal/checkout/success';
@@ -3,7 +3,7 @@ import {
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import shared from '../../../../../../website/common';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
describe('payments : paypal #subscribe', () => {
const endpoint = '/paypal/subscribe';
@@ -1,7 +1,7 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
describe('payments : paypal #subscribeSuccess', () => {
@@ -7,7 +7,7 @@ import {
} from '../../../../helpers/api-integration/v3';
import { quests as questScrolls } from '../../../../../website/common/script/content/quests';
import { chatModel as Chat } from '../../../../../website/server/models/message';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
let questingGroup;
@@ -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 () => {
@@ -1,5 +1,5 @@
import { v4 as generateUUID } from 'uuid';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
import {
generateUser,
sleep,
@@ -8,7 +8,7 @@ import {
generateChallenge,
sleep,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('POST /user/class/cast/:spellId', () => {
let user;
@@ -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', () => {
@@ -108,6 +108,20 @@ describe('PUT /user/auth/update-email', () => {
const isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
expect(isValidPassword).to.equal(true);
});
it('invalidates any outstanding password reset code', async () => {
await user.updateOne({
'auth.local.passwordResetCode': 'impossible-reset-code',
});
await user.put(ENDPOINT, {
newEmail: 'bogo@example.com',
password: oldPassword,
});
await user.sync();
expect(user.auth.local.passwordResetCode).to.not.exist;
});
});
context('Social Login User', async () => {
@@ -5,12 +5,13 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
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 () => {
@@ -3,7 +3,7 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
describe('POST /user/buy-gear/:key', () => {
let user;
@@ -3,7 +3,7 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
const { content } = shared;
@@ -3,7 +3,7 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
const { content } = shared;
@@ -2,7 +2,7 @@ import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../../website/server/libs/apiError';
describe('POST /user/allocate', () => {
let user;
@@ -3,7 +3,7 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('POST /user/webhook', () => {
let user; let
@@ -3,7 +3,7 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
import { apiError } from '../../../../../website/server/libs/apiError';
describe('PUT /user/webhook/:id', () => {
let user; let
@@ -1,5 +1,3 @@
import { TAVERN_ID } from '../../../../../website/server/models/group';
import { updateDocument } from '../../../../helpers/mongo';
import {
requester,
resetHabiticaDB,
@@ -18,7 +16,9 @@ describe('GET /world-state', () => {
});
it('returns Tavern quest data when world boss is active', async () => {
await updateDocument('groups', { _id: TAVERN_ID }, { quest: { active: true, key: 'dysheartener', progress: { hp: 50000, rage: 9999 } } });
sinon.stub(worldState, 'getWorldBoss').returns({
active: true, extra: {}, key: 'dysheartener', progress: { hp: 50000, rage: 9999, collect: {} },
});
const res = await requester().get('/world-state');
expect(res).to.have.nested.property('worldBoss');
@@ -33,15 +33,29 @@ describe('GET /world-state', () => {
rage: 9999,
},
});
worldState.getWorldBoss.restore();
});
it('calls getRepeatingEvents for data', async () => {
const getRepeatingEventsOnDate = sinon.stub(common.content, 'getRepeatingEventsOnDate').returns([]);
const getCurrentGalaEvent = sinon.stub(common.schedule, 'getCurrentGalaEvent').returns({});
await requester().get('/world-state');
expect(getRepeatingEventsOnDate).to.have.been.calledOnce;
expect(getCurrentGalaEvent).to.have.been.calledOnce;
getRepeatingEventsOnDate.restore();
getCurrentGalaEvent.restore();
});
context('no current event', () => {
beforeEach(async () => {
sinon.stub(worldState, 'getCurrentEvent').returns(null);
sinon.stub(worldState, 'getCurrentEventList').returns([]);
});
afterEach(() => {
worldState.getCurrentEvent.restore();
worldState.getCurrentEventList.restore();
});
it('returns null for the current event when there is none active', async () => {
@@ -51,18 +65,18 @@ describe('GET /world-state', () => {
});
});
context('no current event', () => {
context('active event', () => {
const evt = {
...common.content.events.fall2020,
event: 'fall2020',
};
beforeEach(async () => {
sinon.stub(worldState, 'getCurrentEvent').returns(evt);
sinon.stub(worldState, 'getCurrentEventList').returns([evt]);
});
afterEach(() => {
worldState.getCurrentEvent.restore();
worldState.getCurrentEventList.restore();
});
it('returns the current event when there is an active one', async () => {
@@ -71,4 +85,45 @@ describe('GET /world-state', () => {
expect(res.currentEvent).to.eql(evt);
});
});
context('active event with NPC image suffix', () => {
const evt = {
...common.content.events.fall2020,
event: 'fall2020',
npcImageSuffix: 'fall',
};
beforeEach(async () => {
sinon.stub(worldState, 'getCurrentEventList').returns([evt]);
});
afterEach(() => {
worldState.getCurrentEventList.restore();
});
it('returns the NPC image suffix when present', async () => {
const res = await requester().get('/world-state');
expect(res.npcImageSuffix).to.equal('fall');
});
it('returns the NPC image suffix with multiple events present', async () => {
const evt2 = {
...common.content.events.winter2020,
event: 'test',
};
const evt3 = {
...common.content.events.winter2020,
event: 'winter2020',
npcImageSuffix: 'winter',
};
worldState.getCurrentEventList.returns([evt, evt2, evt3]);
const res = await requester().get('/world-state');
expect(res.npcImageSuffix).to.equal('winter');
});
});
});
@@ -8,7 +8,7 @@ import {
generateChallenge,
sleep,
} from '../../../helpers/api-integration/v4';
import apiError from '../../../../website/server/libs/apiError';
import { apiError } from '../../../../website/server/libs/apiError';
describe('POST /user/class/cast/:spellId', () => {
let user;
+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);
});
});
});
});
});
});
+430
View File
@@ -0,0 +1,430 @@
import shared from '../../../website/common';
import {
generateUser,
} from '../../helpers/common.helper';
import seasonalConfig from '../../../website/common/script/libs/shops-seasonal.config';
describe('shops', () => {
const user = generateUser();
let clock;
afterEach(() => {
if (clock) {
clock.restore();
}
user.achievements.quests = {};
});
describe('market', () => {
const shopCategories = shared.shops.getMarketCategories(user);
it('contains at least the 3 default categories', () => {
expect(shopCategories.length).to.be.greaterThan(2);
});
it('does not contain an empty category', () => {
_.each(shopCategories, category => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('does not duplicate identifiers', () => {
const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
expect(identifiers.length).to.eql(shopCategories.length);
});
it('items contain required fields', () => {
_.each(shopCategories, category => {
_.each(category.items, item => {
_.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'class'], key => {
expect(_.has(item, key)).to.eql(true);
});
});
});
});
describe('premium hatching potions', () => {
it('contains current scheduled premium hatching potions', async () => {
clock = sinon.useFakeTimers(new Date('2024-04-01'));
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.length).to.eql(2);
});
it('does not contain past scheduled premium hatching potions', async () => {
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.filter(x => x.key === 'Aquatic' || x.key === 'Celestial').length).to.eql(0);
});
it('returns end date for scheduled premium potions', async () => {
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
potions.items.forEach(potion => {
expect(potion.end).to.exist;
});
});
it('contains unlocked quest premium hatching potions', async () => {
user.achievements.quests = {
bronze: 1,
blackPearl: 1,
};
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(2);
});
it('does not contain locked quest premium hatching potions', async () => {
clock = sinon.useFakeTimers(new Date('2024-04-01'));
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
expect(potions.items.length).to.eql(2);
expect(potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').length).to.eql(0);
});
it('does not return end date for quest premium potions', async () => {
user.achievements.quests = {
bronze: 1,
blackPearl: 1,
};
const potions = shared.shops.getMarketCategories(user).find(x => x.identifier === 'premiumHatchingPotions');
potions.items.filter(x => x.key === 'Bronze' || x.key === 'BlackPearl').forEach(potion => {
expect(potion.end).to.not.exist;
});
});
});
it('does not return items with event data', async () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.event).to.not.exist;
expect(item.season).to.not.exist;
});
});
});
it('shows relevant non class gear in special category', () => {
const contributor = generateUser({
contributor: {
level: 7,
critical: true,
},
items: {
gear: {
owned: {
weapon_armoire_basicCrossbow: true, // eslint-disable-line camelcase
},
},
},
});
const gearCategories = shared.shops.getMarketGearCategories(contributor);
const specialCategory = gearCategories.find(o => o.identifier === 'none');
expect(specialCategory.items.find(item => item.key === 'weapon_special_1'), 'weapon_special_1');
expect(specialCategory.items.find(item => item.key === 'armor_special_1'), 'armor_special_1');
expect(specialCategory.items.find(item => item.key === 'head_special_1'), 'head_special_1');
expect(specialCategory.items.find(item => item.key === 'shield_special_1'), 'shield_special_1');
expect(specialCategory.items.find(item => item.key === 'weapon_special_critical'), 'weapon_special_critical');
expect(specialCategory.items.find(item => item.key === 'weapon_armoire_basicCrossbow'), 'weapon_armoire_basicCrossbow');// eslint-disable-line camelcase
});
describe('handles seasonal gear', () => {
beforeEach(() => {
clock = sinon.useFakeTimers(new Date('2024-04-01'));
});
it('shows current seasonal gear for warriors', () => {
const warriorItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'warrior').items.filter(x => x.key.indexOf('spring2024') !== -1);
expect(warriorItems.length, 'Warrior seasonal gear').to.eql(4);
});
it('shows current seasonal gear for mages', () => {
const mageItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'wizard').items.filter(x => x.key.indexOf('spring2024') !== -1);
expect(mageItems.length, 'Mage seasonal gear').to.eql(3);
});
it('shows current seasonal gear for healers', () => {
const healerItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'healer').items.filter(x => x.key.indexOf('spring2024') !== -1);
expect(healerItems.length, 'Healer seasonal gear').to.eql(4);
});
it('shows current seasonal gear for rogues', () => {
const rogueItems = shared.shops.getMarketGearCategories(user).find(x => x.identifier === 'rogue').items.filter(x => x.key.indexOf('spring2024') !== -1);
expect(rogueItems.length, 'Rogue seasonal gear').to.eql(4);
});
it('seasonal gear contains end date', () => {
const categories = shared.shops.getMarketGearCategories(user);
categories.forEach(category => {
category.items.filter(x => x.key.indexOf('spring2024') !== -1).forEach(item => {
expect(item.end, item.key).to.exist;
});
});
});
it('only shows gear for the current season', () => {
const categories = shared.shops.getMarketGearCategories(user);
categories.forEach(category => {
const otherSeasons = category.items.filter(item => item.key.indexOf('winter') !== -1 || item.key.indexOf('summer') !== -1 || item.key.indexOf('fall') !== -1);
expect(otherSeasons.length).to.eql(0);
});
});
it('does not show gear from past seasons', () => {
const categories = shared.shops.getMarketGearCategories(user);
categories.forEach(category => {
const otherYears = category.items.filter(item => item.key.indexOf('spring') !== -1 && item.key.indexOf('2024') === -1);
expect(otherYears.length).to.eql(0);
});
});
});
it('does not show gear when it is all owned', () => {
const userWithItems = generateUser({
stats: {
class: 'wizard',
},
items: {
gear: {
owned: {
weapon_wizard_0: true, // eslint-disable-line camelcase
weapon_wizard_1: true, // eslint-disable-line camelcase
weapon_wizard_2: true, // eslint-disable-line camelcase
weapon_wizard_3: true, // eslint-disable-line camelcase
weapon_wizard_4: true, // eslint-disable-line camelcase
weapon_wizard_5: true, // eslint-disable-line camelcase
weapon_wizard_6: true, // eslint-disable-line camelcase
armor_wizard_1: true, // eslint-disable-line camelcase
armor_wizard_2: true, // eslint-disable-line camelcase
armor_wizard_3: true, // eslint-disable-line camelcase
armor_wizard_4: true, // eslint-disable-line camelcase
armor_wizard_5: true, // eslint-disable-line camelcase
head_wizard_1: true, // eslint-disable-line camelcase
head_wizard_2: true, // eslint-disable-line camelcase
head_wizard_3: true, // eslint-disable-line camelcase
head_wizard_4: true, // eslint-disable-line camelcase
head_wizard_5: true, // eslint-disable-line camelcase
},
},
},
});
const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.length).to.eql(0);
});
it('shows available gear not yet purchased and previously owned', () => {
const userWithItems = generateUser({
stats: {
class: 'wizard',
},
items: {
gear: {
owned: {
weapon_wizard_0: true, // eslint-disable-line camelcase
weapon_wizard_1: true, // eslint-disable-line camelcase
weapon_wizard_2: true, // eslint-disable-line camelcase
weapon_wizard_3: true, // eslint-disable-line camelcase
weapon_wizard_4: true, // eslint-disable-line camelcase
armor_wizard_1: true, // eslint-disable-line camelcase
armor_wizard_2: true, // eslint-disable-line camelcase
armor_wizard_3: false, // eslint-disable-line camelcase
armor_wizard_4: false, // eslint-disable-line camelcase
head_wizard_1: true, // eslint-disable-line camelcase
head_wizard_2: false, // eslint-disable-line camelcase
head_wizard_3: true, // eslint-disable-line camelcase
head_wizard_4: false, // eslint-disable-line camelcase
head_wizard_5: true, // eslint-disable-line camelcase
},
},
},
});
const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);
expect(shopWizardItems.find(item => item.key === 'armor_wizard_3').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'armor_wizard_4').locked).to.eql(true);
expect(shopWizardItems.find(item => item.key === 'head_wizard_2').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'head_wizard_4').locked).to.eql(true);
});
});
describe('questShop', () => {
const shopCategories = shared.shops.getQuestShopCategories(user);
it('does not contain an empty category', () => {
_.each(shopCategories, category => {
expect(category.items.length, category.identifier).to.be.greaterThan(0);
});
});
it('does not duplicate identifiers', () => {
const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
expect(identifiers.length).to.eql(shopCategories.length);
});
it('items contain required fields', () => {
_.each(shopCategories, category => {
if (category.identifier === 'bundle') {
_.each(category.items, item => {
_.each(['key', 'text', 'notes', 'value', 'currency', 'purchaseType', 'class'], key => {
expect(_.has(item, key)).to.eql(true);
});
});
} else {
_.each(category.items, item => {
_.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'boss', 'class', 'collect', 'drop', 'unlockCondition', 'lvl'], key => {
expect(_.has(item, key)).to.eql(true);
});
});
}
});
});
it('does not return items with event data', async () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.event).to.not.exist;
});
});
});
});
describe('timeTravelers', () => {
const shopCategories = shared.shops.getTimeTravelersCategories(user);
it('does not contain an empty category', () => {
_.each(shopCategories, category => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('does not duplicate identifiers', () => {
const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
expect(identifiers.length).to.eql(shopCategories.length);
});
it('items contain required fields', () => {
_.each(shopCategories, category => {
_.each(category.items, item => {
_.each(['key', 'text', 'value', 'currency', 'locked', 'purchaseType', 'class', 'notes', 'class'], key => {
expect(_.has(item, key)).to.eql(true);
});
});
});
});
it('does not return items with event data', async () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.event).to.not.exist;
});
});
});
it('returns pets', () => {
const pets = shopCategories.find(cat => cat.identifier === 'pets').items;
expect(pets.length).to.be.greaterThan(0);
});
it('returns mounts', () => {
const mounts = shopCategories.find(cat => cat.identifier === 'mounts').items;
expect(mounts.length).to.be.greaterThan(0);
});
it('returns quests', () => {
const quests = shopCategories.find(cat => cat.identifier === 'quests').items;
expect(quests.length).to.be.greaterThan(0);
});
it('returns backgrounds', () => {
const backgrounds = shopCategories.find(cat => cat.identifier === 'backgrounds').items;
expect(backgrounds.length).to.be.greaterThan(0);
});
});
describe('customizationShop', () => {
const shopCategories = shared.shops.getCustomizationsShopCategories(user, null);
it('does not return items with event data', async () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.event, item.key).to.not.exist;
});
});
});
it('backgrounds category contains end date', () => {
const backgroundCategory = shopCategories.find(cat => cat.identifier === 'backgrounds');
expect(backgroundCategory.end).to.exist;
expect(backgroundCategory.end).to.be.greaterThan(new Date());
});
it('hair color category contains end date', () => {
const colorCategory = shopCategories.find(cat => cat.identifier === 'color');
expect(colorCategory.end).to.exist;
expect(colorCategory.end).to.be.greaterThan(new Date());
});
it('skin category contains end date', () => {
const colorCategory = shopCategories.find(cat => cat.identifier === 'color');
expect(colorCategory.end).to.exist;
expect(colorCategory.end).to.be.greaterThan(new Date());
});
});
describe('seasonalShop', () => {
const shopCategories = shared.shops.getSeasonalShopCategories(user, null, seasonalConfig());
const today = new Date();
it('does not contain an empty category', () => {
_.each(shopCategories, category => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('does not duplicate identifiers', () => {
const identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
expect(identifiers.length).to.eql(shopCategories.length);
});
it('does not return items with event data', async () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.event, item.key).to.not.exist;
});
});
});
it('items contain required fields', () => {
_.each(shopCategories, category => {
_.each(category.items, item => {
_.each(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'type'], key => {
expect(_.has(item, key), item.key).to.eql(true);
});
});
});
});
it('items have a valid end date', () => {
shopCategories.forEach(category => {
category.items.forEach(item => {
expect(item.end, item.key).to.be.a('date');
expect(item.end, item.key).to.be.greaterThan(today);
});
});
});
it('items match current season', () => {
const currentSeason = seasonalConfig().currentSeason.toLowerCase();
shopCategories.forEach(category => {
category.items.forEach(item => {
if (item.klass === 'special') {
expect(item.season, item.key).to.eql(currentSeason);
}
});
});
});
});
});
+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: {
@@ -9,7 +9,7 @@ import {
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import content from '../../../../website/common/script/content/index';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buy', () => {
let user;
@@ -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;
});
});
@@ -11,7 +11,7 @@ import {
BadRequest, NotAuthorized, NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
async function buyGear (user, req, analytics) {
const buyOp = new BuyMarketGearOperation(user, req, analytics);
@@ -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');
}
});
});
});
@@ -10,11 +10,12 @@ import {
NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
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;
@@ -8,7 +8,7 @@ import {
NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buyQuest', () => {
let user;
-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'));
}
});
});
});
@@ -8,7 +8,7 @@ import content from '../../../../website/common/script/content/index';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
import { BuyHourglassMountOperation } from '../../../../website/common/script/ops/buy/buyMount';
describe('common.ops.hourglassPurchase', () => {
@@ -15,6 +15,7 @@ import {
describe('shared.ops.purchase', () => {
const SEASONAL_FOOD = moment().isBefore('2021-11-02T20:00-04:00') ? 'Candy_Base' : 'Meat';
let user;
let clock;
const goldPoints = 40;
const analytics = { track () {} };
@@ -25,11 +26,13 @@ describe('shared.ops.purchase', () => {
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
clock = sandbox.useFakeTimers(new Date('2024-01-10'));
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
clock.restore();
});
context('failure conditions', () => {
@@ -82,13 +85,77 @@ describe('shared.ops.purchase', () => {
it('returns error when user does not have enough gems to buy an item', async () => {
try {
await purchase(user, { params: { type: 'gear', key: 'headAccessory_special_wolfEars' } });
await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2019Healer' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughGems'));
}
});
it('returns error when gear is not available', async () => {
try {
await purchase(user, { params: { type: 'gear', key: 'shield_special_spring2019Healer' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns an error when purchasing current seasonal gear', async () => {
user.balance = 2;
try {
await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2024Healer' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns error when hatching potion is not available', async () => {
try {
await purchase(user, { params: { type: 'hatchingPotions', key: 'PolkaDot' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns error when quest for hatching potion was not yet completed', async () => {
try {
await purchase(user, { params: { type: 'hatchingPotions', key: 'BlackPearl' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns error when quest for egg was not yet completed', async () => {
try {
await purchase(user, { params: { type: 'eggs', key: 'Octopus' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns error when bundle is not available', async () => {
try {
await purchase(user, { params: { type: 'bundles', key: 'forestFriends' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notAvailable'));
}
});
it('returns error when gear is not gem purchasable', async () => {
try {
await purchase(user, { params: { type: 'gear', key: 'shield_healer_3' } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAvailable'));
}
});
it('returns error when item is not found', async () => {
const params = { key: 'notExisting', type: 'food' };
@@ -99,44 +166,6 @@ describe('shared.ops.purchase', () => {
expect(err.message).to.equal(i18n.t('contentKeyNotFound', params));
}
});
it('returns error when user supplies a non-numeric quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
try {
await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
it('returns error when user supplies a negative quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
it('returns error when user supplies a decimal quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
});
context('successful purchase', () => {
@@ -150,7 +179,7 @@ describe('shared.ops.purchase', () => {
user.pinnedItems.push({ type: 'eggs', key: 'Wolf' });
user.pinnedItems.push({ type: 'hatchingPotions', key: 'Base' });
user.pinnedItems.push({ type: 'food', key: SEASONAL_FOOD });
user.pinnedItems.push({ type: 'gear', key: 'headAccessory_special_tigerEars' });
user.pinnedItems.push({ type: 'gear', key: 'shield_special_winter2019Healer' });
user.pinnedItems.push({ type: 'bundles', key: 'featheredFriends' });
});
@@ -185,9 +214,9 @@ describe('shared.ops.purchase', () => {
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
it('purchases gear', async () => {
it('purchases past seasonal gear', async () => {
const type = 'gear';
const key = 'headAccessory_special_tigerEars';
const key = 'shield_special_winter2019Healer';
await purchase(user, { params: { type, key } });
@@ -195,9 +224,39 @@ describe('shared.ops.purchase', () => {
expect(pinnedGearUtils.removeItemByPath.calledOnce).to.equal(true);
});
it('purchases hatching potion', async () => {
const type = 'hatchingPotions';
const key = 'Peppermint';
await purchase(user, { params: { type, key } });
expect(user.items.hatchingPotions[key]).to.eql(1);
});
it('purchases hatching potion if user completed quest', async () => {
const type = 'hatchingPotions';
const key = 'Bronze';
user.achievements.quests.bronze = 1;
await purchase(user, { params: { type, key } });
expect(user.items.hatchingPotions[key]).to.eql(1);
});
it('purchases egg if user completed quest', async () => {
const type = 'eggs';
const key = 'Deer';
user.achievements.quests.ghost_stag = 1;
await purchase(user, { params: { type, key } });
expect(user.items.eggs[key]).to.eql(1);
});
it('purchases quest bundles', async () => {
const startingBalance = user.balance;
const clock = sandbox.useFakeTimers(moment('2024-03-20').valueOf());
clock.restore();
clock = sandbox.useFakeTimers(moment('2022-03-10').valueOf());
const type = 'bundles';
const key = 'cuddleBuddies';
const price = 1.75;
@@ -216,7 +275,6 @@ describe('shared.ops.purchase', () => {
expect(user.balance).to.equal(startingBalance - price);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
clock.restore();
});
});
@@ -257,5 +315,43 @@ describe('shared.ops.purchase', () => {
expect(user.items[type][key]).to.equal(2);
});
it('returns error when user supplies a non-numeric quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
try {
await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
it('returns error when user supplies a negative quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
it('returns error when user supplies a decimal quantity', async () => {
const type = 'eggs';
const key = 'Wolf';
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
}
});
});
});
+1 -1
View File
@@ -9,7 +9,7 @@ import i18n from '../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.feed', () => {
+1 -1
View File
@@ -8,7 +8,7 @@ import i18n from '../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.hatch', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import sleep from '../../../website/common/script/ops/sleep';
import { sleep } from '../../../website/common/script/ops/sleep';
import {
generateUser,
} from '../../helpers/common.helper';
+1 -1
View File
@@ -7,7 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.allocate', () => {
let user;
+1 -1
View File
@@ -7,7 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.allocateBulk', () => {
let user;
@@ -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);
});
});

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