Compare commits

..

138 Commits

Author SHA1 Message Date
Sabe Jones 848883736d 4.10.1 2017-11-09 21:23:55 +00:00
Sabe Jones 28149202db chore(i18n): update locales 2017-11-09 17:53:06 +00:00
Alys 4b23cd9f23 add missing string for dateEndNovember 2017-11-09 17:46:12 +00:00
Alys 8aaabdc086 add missing removeInvite string 2017-11-09 17:45:59 +00:00
SabreCat 6e6ca05352 fix(sprites): missing Guild image 2017-11-09 17:44:48 +00:00
Sabe Jones 8d1ebff7e9 4.10.0 2017-11-07 23:55:16 +00:00
Sabe Jones 993df72708 chore(i18n): update locales 2017-11-07 23:53:47 +00:00
SabreCat 50d3226a86 feat(content): change to Thunderstorm Hatching Potions 2017-11-07 22:02:46 +00:00
Keith Holliday f964e3c0a5 Updated avatar menu icons and style (#9409) 2017-11-07 11:57:42 -07:00
Matteo Pagliazzi f25fe9e263 remove forked vue version 2017-11-06 23:46:18 +00:00
Sabe Jones 6eb06fb054 4.9.1 2017-11-03 22:58:12 +00:00
Keith Holliday 286c8c7530 Ensured sort value is true for checklists (#9386) 2017-11-03 15:50:11 -05:00
Keith Holliday 47ab8f2073 Added exitence checks (#9383) 2017-11-03 15:38:05 -05:00
Sabe Jones 83353f6481 Merge branch 'paglias/fix-sprites' into release 2017-11-03 20:35:20 +00:00
Matteo Pagliazzi 9cbd7ad62d fix customize-options sprites 2017-11-03 19:06:06 +01:00
thehollidayinn 3485a1d0bc Reloaded completed todos if we are unlinking a todo 2017-11-03 10:33:20 -06:00
thehollidayinn 2e5106fda1 Removed old state item 2017-11-03 10:24:46 -06:00
thehollidayinn 2e5f5714e4 Added broken task event 2017-11-03 10:24:00 -06:00
Sabe Jones 3cf7b2c96c 4.9.0 2017-11-03 03:04:52 +00:00
Sabe Jones 286db39478 chore(i18n): update locales 2017-11-03 03:04:15 +00:00
SabreCat 4d4c1cfaf3 chore(sprites): compile 2017-11-03 02:54:26 +00:00
SabreCat d7ad3efabf fix(news): proper Take This announcement 2017-11-03 02:49:41 +00:00
SabreCat f8876fe055 feat(content): Backgrounds and Armoire 2017-11
End Habitoween and Fall Festival
2017-11-03 01:56:15 +00:00
Keith Holliday b973335d69 Added extra months to account for months with larger amount of days (#9379) 2017-11-02 15:44:24 -06:00
Phillip Thelen 3b6fce0708 Add "stats" as a tutorial step (#9377)
This is used mostly for the mobile apps to identify if the tutorial step about stats has be shown yet or not.
2017-11-02 15:57:16 -05:00
Keith Holliday ff6bd6de71 Ensured user selects plan (#9378) 2017-11-02 14:26:00 -06:00
Keith Holliday 042afe1df3 Add close to tags popup (#9376) 2017-11-02 14:25:37 -06:00
Matteo Pagliazzi a208ba4aba Tasks v2 Part 2 (#9236)
* start updating colors for tasks controls

* finish updating controls colors

* change hoevr behavior

* change transition duration

* update color with transition

* refactor menu wip

* wip

* upgrade vue deps

* fix warnings

* fix menu

* misc fixes

* more fixes

* fix badge

* fix margins in menu

* wip tooltips

* tooltips

* fix checklist colors

* add badges

* fix quick add input

* add transition to task controls too

* add batch add

* fix task filtering

* finish tasks badges

* fix menu

* upgrade deps

* fix shop items using all the same image

* fix animation

* disable client tests until we remove phantomjs

* revert changes to tasks colors

* fix opacity in task modal inputs

* remove client unit tests from travis

* wip task dropdown

* fix z-index for task footer/header

* fixes and add files

* fixes

* bigger clickable area

* more space to open task dropdown

* droddown position

* fix menu position

* make sure other dropdowns get closed correctly

* fixes

* start to fix z-index

* draggable = false for task dropdown

* fix for dropdown position

* implement move to top / bottom

* fix push to bottom

* typo

* fix drag and drop

* use standard code

* wider click area for dropdown

* unify badge look

* fix padding

* misc fixes

* more fixes

* make dropdown scrollable

* misc fixes

* fix padding for notes

* use existing code instead of new props
2017-11-02 21:07:38 +01:00
Keith Holliday 0e958fd306 Prevented challenge edit during RYA (#9373) 2017-11-02 11:37:29 -06:00
Keith Holliday d98fe79e9c Fixed member modal search (#9375) 2017-11-02 11:36:08 -06:00
Keith Holliday 0e5a811b98 Prevented challenge prize edit. Fixed edit to create modal (#9374) 2017-11-02 11:30:01 -06:00
Keith Holliday a28aea65f8 Added reward text (#9332) 2017-11-01 10:03:23 -06:00
Keith Holliday 0f92349902 Added fix for multiple level up (#9330) 2017-11-01 10:01:58 -06:00
Sabe Jones d4881cb73a Merge branch 'release' into develop 2017-10-31 19:22:58 +00:00
Sabe Jones b3216fdb85 4.8.0 2017-10-31 19:22:27 +00:00
Keith Holliday 3e37941e0a Bulk stats (#9260)
* Reorganized stats

* Organized allocation common code

* Added bulk allocate to common

* Added allocate bulk route

* Fixed structure and lint

* Fixed import and apidoc
2017-10-31 12:57:44 -06:00
Alys 32088767ac update github templates for new website and wiki page locations 2017-10-31 21:25:01 +10:00
Alys f4d021ab8c fix an inaccurate comment about guilds needing summaries 2017-10-31 20:54:23 +10:00
SabreCat 8532203717 Merge branch 'release' into develop 2017-10-31 01:03:07 +00:00
SabreCat 365daba6fc 4.7.1 2017-10-31 00:49:58 +00:00
SabreCat 70692752c7 fix(sprites): bad shirt centering 2017-10-31 00:48:37 +00:00
Sabe Jones 95d4016678 Merge branch 'release' into develop 2017-10-30 20:50:32 +00:00
Sabe Jones 061457b268 4.7.0 2017-10-30 20:50:11 +00:00
Sabe Jones e7ec9a6d65 chore(i18n): update locales 2017-10-30 20:49:09 +00:00
SabreCat d1396e7bc6 chore(sprites): compile 2017-10-30 20:41:32 +00:00
SabreCat d5cedaa925 feat(event): Habitoween 2017-10-30 20:40:29 +00:00
Sabe Jones bea813b318 Merge branch 'release' into develop 2017-10-26 22:47:18 +00:00
Sabe Jones b31268fbc2 4.6.3 2017-10-26 22:46:51 +00:00
thehollidayinn 35727228f0 Removed autorefresh 2017-10-26 17:20:52 -05:00
SabreCat feb7ab8345 Merge branch 'release' into develop 2017-10-26 21:16:09 +00:00
SabreCat f4422b8d6c 4.6.2 2017-10-26 21:14:13 +00:00
SabreCat 2d4dc9e23c chore(news): blog Bailey 2017-10-26 21:13:04 +00:00
Tyler Nychka bba2e71af3 Task order fix (#8928) 2017-10-26 10:37:51 -05:00
Mel 26123ac6ae API - Get challenges for a group does not allow party or habitrpg (#8882)
* test get party challenges by party ID

* tavern challenge tests; failing tests with ID 'party' or 'habitrpg'

* allow finding challenges by groupid 'party' or 'habitrpg'

* use single quotes in strings
2017-10-25 15:35:23 -05:00
Zobdek addee73e4d Set _cronSignature to current time instead of uuid (#8565)
* Changes made to satisfy #8163. _cronSignature is set to current time when cron starts so that if cron fails to set _cronSignature to 'NOT_RUNNING' for any reason a new cron can be started after a set amount of time (1 hour for now)

* fix lint errors

* changed cronTimeout to CRON_TIMEOUT

* Changed variable names and comments to be more clear

* Fixed stub for failing test so that it matches new mongo db update call signature

* First pass at unit tests, error messages and some other things need to be determined

* Fixed a tab that snuck in :/

* Fixed lint issues (issues with spaces)

* Fix infix operator spacing

* Created constant. Make sure cron failure test verifies that it is failing for the right reason

* Fixed lint errors

* Removed no longer used uuid import
2017-10-25 15:29:16 -05:00
Keith Holliday c39505d41c Hotfix/add autoreload (#9269)
* Added auto refresh on server update back

* Fixed lint

* Updated copy
2017-10-25 15:22:26 -05:00
Lachlan Heywood 9736ef0d25 Add ESLint to gulp scripts (#9259)
* Remove gulp/* and gulpfile from ESLint ignores

* Update .eslintrc in local gulp folder

* Start work on refactoring gulp files

* add radix

* Add line-specific eslint exceptions

* removed redundant eslint file for gulp

* add more exceptions

* Add exceptions to main gulpfile.js
2017-10-25 10:45:03 +02:00
Sabe Jones 638259b885 Merge branch 'release' into develop 2017-10-24 22:42:20 +00:00
Sabe Jones c24545cae5 4.6.1 2017-10-24 22:41:58 +00:00
Keith Holliday 6bc70ca471 Hotfix/oct 24 fixes (#9261)
* Removed max height from text section

* Added items to mystery sets

* Group manager fixes

* Fixed promo code apply
2017-10-24 17:41:36 -05:00
Sabe Jones d2f0d7b20b Merge branch 'release' into develop 2017-10-24 20:36:24 +00:00
Sabe Jones b445bc8261 4.6.0 2017-10-24 20:35:17 +00:00
Sabe Jones 31281b43d3 chore(i18n): update locales 2017-10-24 20:34:47 +00:00
SabreCat a8a915ea8e chore(sprites): compile 2017-10-24 20:23:42 +00:00
SabreCat dc3ee25e65 feat(content): subscriber items
Also fix(sprites): resize and recanvas gift boxes
2017-10-24 20:21:30 +00:00
Lula Villalobos 3c9f7ff9d8 Bug Fix on ScoreTask: Gp added instead of append [fixes #9180] (#9207)
* added parseInt to stats.gp so it can add the new value

* added radix parameter to fix lint issue

* revert changes in scoreTask.js

* convert restoreValues into numbers before setting to users.stats
2017-10-24 16:23:12 +02:00
Sabe Jones 54f57445da 4.5.0 2017-10-23 22:40:41 +00:00
Sabe Jones 95ef2b1789 Merge branch 'develop' into release 2017-10-23 22:40:30 +00:00
Keith Holliday 4d32977e5c Quick fix (#9257)
* Removed hard refresh

* Changed sorting to happen during compute
2017-10-23 17:27:26 -05:00
Keith Holliday 7fe2504906 Bulk purchasing (#9196)
* Moved buy tests

* Added mystery buy to buy.js

* Added quest purchasing to buy

* Added buy special

* Moved integration tests to buy folder

* Removed buyGear dependency

* Removed buyArmoire dependency

* Removed buyHealthPotion dependency

* Removed myster, quest and special dependency

* Replaced functions with factory

* Added bulk purchasing to common

* Added bulk purchasing to the api

* Added bulk purchasing to client

* Refactored purchasing function to reduce long method

* Added bulk purchase to gem purchases

* Added bulk purchasing to api

* Added bulk purchasing to gem items on client

* Removed bulk from equipment

* Removed recentlyPurchased

* Fixed style issues and prevented puchasing more gems than are left

* Fixed lint

* Fixed missing keys

* Fixed gem amount notice

* Added quest modal to pinned item

* Added bulk purchase to gem modal

* Fixed styles

* Fixed bulk purchase for spells

* Fixed modal size

* Hid autofill
2017-10-23 16:05:35 -05:00
Matteo Pagliazzi b74cee3d21 API Token Changes (#9202)
* hide API token by default

* wip

* add route to reset the api token

* remove dead code
2017-10-23 22:58:33 +02:00
Keith Holliday 5af7733150 Task order fixes (#9255)
* Reset todo task order

* Disabled sorting on todo due

* Revert to task order

* Fixed task sorting sync with server
2017-10-23 12:59:52 -05:00
Keith Holliday 824bf62e0a Force refresh is server version is updated (#9239)
* Force refresh is server version is updated

* Added reload true

* Added confirmation of update

* Forced refresh on cron

* Updated response tests
2017-10-23 12:58:11 -05:00
Keith Holliday ac24a5dddd Moved ponytails to styles (#9254) 2017-10-22 15:48:50 -05:00
Alys 9111f59da4 change price for backgrounds set from 5 to 15 2017-10-22 09:24:32 +10:00
Sabe Jones 6a550b34df 4.4.5 2017-10-21 03:35:51 +00:00
SabreCat 0c973b1cf0 chore(news): Bailey 2017-10-21 03:33:42 +00:00
SabreCat 4170ef5e79 Merge branch 'release' into develop 2017-10-21 03:23:13 +00:00
Keith Holliday 506275c30e Oct 19 fixes (#9234)
* Add more checks for user achievements

* Began to add ajax request for bailey

* Prevented purchase of locked item

* Refactored notifications to have unique id and use actions

* Added feature banner when gear is bought

* Removed debug code

* Mark group notifications as read

* Fixed lint issues

* Added gem icon to purchase all
2017-10-20 15:22:13 +02:00
Alys 6838b7d0a6 remove duplicated word "caused" from Masterclassers quest text 2017-10-20 23:12:26 +10:00
Sabe Jones e987cd52a6 4.4.4 2017-10-18 21:06:05 +00:00
Sabe Jones 1df8668d38 chore(i18n): update locales 2017-10-18 20:57:03 +00:00
Stephy 1af42aa7fe Set a max-width for Markdown images (#9230)
Prevents images from overflowing in tavern chat, guild sidebars, tasks,
and anywhere else using Markdown

Closes https://github.com/HabitRPG/habitica/issues/9171
2017-10-18 15:47:16 -05:00
maurnoc 9dc9faa70d fixes bug #9173 notification menu having unneeded scroll bars (#9229) 2017-10-18 15:44:50 -05:00
Cezary Piątek 7fbcc0a263 Replace raw HTML internal hyperlinks with router links #9209 (#9212)
* Replace raw HTML internal hyperlinks with router links #9209

* Fix improper route-link
2017-10-18 15:40:53 -05:00
borisabramovich86 0450e9c3ae Added empty state values for photo and description in user profile (#9201) 2017-10-18 15:36:26 -05:00
borisabramovich86 15626b8ae1 Prevent user from blocking himself - fixes #9097 (#9194)
* Added check - user can't block himself

* removed exclusive test
2017-10-18 15:26:01 -05:00
borisabramovich86 444f393f3a stops Orb of Rebirth being visible after rebirth - fixes #8593 (#9172)
* remove link between rebirth and beastmaster, and fix tests

* add test checking rebirth is reset when beastmaster is enabled

* remove exclusive test
2017-10-18 15:23:27 -05:00
q-- d711bf4085 Enable browser spell-checking for task titles (#9100)
This is disabled by default in Firefox for <input> elements.

It can also be enabled document-wide by adding spellcheck=true to the <html> tag, but I haven't done so because I don't know if there might be elements on the page where spell checking is not warranted.

See also: https://developer.mozilla.org/docs/Web/HTML/Global_attributes/spellcheck
2017-10-18 15:13:57 -05:00
John Montgomery 7b93e326fc Re-add the relevant attribute to the Stealth tooltip (#9198) 2017-10-18 23:39:39 +10:00
maurnoc c62ea522c0 duplicate readme (#9219) 2017-10-18 09:41:45 +10:00
Alys 810362a404 fix typo in apidocs comment and my screwup in the previous commit - so sorry :( 2017-10-18 08:13:16 +10:00
Alys 2e429513da fix typo in apidocs comment 2017-10-18 08:04:53 +10:00
Matteo Pagliazzi c8625cb23f Misc fixes (#9215)
* quick add assign tags

* fix columns descriptions

* fix changing language not taking effect immediately
2017-10-17 23:38:42 +02:00
tim1234ltp ad50f90ba0 Bug fixes on Merchandise Page [Fixes Issues #9144] (#9195)
* Moved pictures of merch items from assets/images/merch/ to /website/static/merch
Image src in merch.vue is linked to the new path.

* Rebased and fixed.

* Reverted changes of package-lock.json
2017-10-17 10:48:23 -05:00
Sabe Jones d5305f74e3 4.4.3 2017-10-17 00:01:54 +00:00
Sabe Jones 5ea20e4c8c fix(news): add links 2017-10-17 00:01:39 +00:00
Sabe Jones 9e4e2d0b34 4.4.2 2017-10-16 23:12:41 +00:00
Sabe Jones 6456e6b8e3 Merge branch 'develop' into release 2017-10-16 23:12:30 +00:00
Sabe Jones af961ff16c chore(i18n): update locales 2017-10-16 23:11:52 +00:00
Sabe Jones eb2a6095c2 Fixes 2017/10/16 (#9203)
* fix(various): small issues
* Rename "Gold-Purchasable Quests" to "Masterclasser Quest Lines"
* Use "getting_organized" instead of "organization" as task category
* Use pixel gem instead of SVG gem for Market
* Recanvas shop sprites for Fall 2017 seasonal items

* chore(news): update Bailey
2017-10-16 18:04:49 -05:00
Matteo Pagliazzi ee82f5a774 Client: Tasks and Header v2 (#9168)
* wip: footer

* pin bootstrap-vue

* compact footer and fixes

* compact header

* fix member details in other views

* fix first party member in header

* fix unread notifications badge color

* sync + menu fixes

* loading bar styles

* new filter button

* create button

* create fixes

* quick add

* fix quick add inputs

* tooltips for create buttons

* fix create task moda

* fix class badge

* fixed footer links

* revert change that caused problems with tests and old browsers

* misc
2017-10-16 22:04:16 +02:00
Alys f1c8c4c54b add 'declined' string, needed for quest participants list 2017-10-16 21:41:37 +10:00
Sabe Jones 79b15d7ddf 4.4.1 2017-10-15 17:31:13 +00:00
Trevor Ford f2fed7ea39 fix Stable component to correctly count pets (#9182) 2017-10-15 12:20:40 -05:00
Aivan Monceller ca81ff5af6 #9184 case insensitive search (#9190) 2017-10-15 12:12:30 -05:00
Sabe Jones e28c214696 chore(i18n): update locales 2017-10-15 17:07:09 +00:00
SabreCat 792f45e9bd fix(tasks): incorrect string for text field 2017-10-15 02:37:11 +00:00
Sabe Jones a69c0999d3 Fixes 2017/10/14 (#9191)
* fix(shops): event date range for mobile

* fix(profile): clean up equipment grids

* fix(strings): remove unused string

* fix(sprites): recanvas Lost Masterclasser scrolls
2017-10-14 19:59:28 -05:00
Sabe Jones 37ff35306c 4.4.0 2017-10-14 01:45:57 +00:00
Sabe Jones fd430e95b2 chore(i18n): update locales 2017-10-14 01:45:08 +00:00
Sabe Jones 0ea472b3af Merge branch 'develop' into release 2017-10-14 01:34:12 +00:00
Sabe Jones 6d0496fbd0 Lost Masterclasser Quest Line (#9183)
* fix(sprites): serve animated GIFs
Also add new GIF for upcoming quest line

* feat(content): Masterclasser capstone quest
Also fixes Quests page for clearer lock/unlock display

* chore(sprites): compile

* feat(achievements): award quest completionist cheevo

* feat(quests): Siphoning Void
fix Achievements page styling and text spacing
fix display of Rage bar on party page

* test(quests): Lost Masterclasser

* chore(news): Bailey

* fix(test): only use Masterclasser in relevant scenario
2017-10-13 20:32:55 -05:00
Keith Holliday fac1889776 Oct 11 fxes (#9181)
* Added unqequip for hair styles

* Added quest rewards to quest completed modal

* Fixed display of task approvals

* Ensured the user is welcomed before showing login notification

* Added new message modal

* fixed manager functions

* Fixed group edit on group plan
2017-10-13 06:40:53 +02:00
SabreCat 9ca6da0f75 fix(sprites): include sprite CSS in new client build 2017-10-12 18:57:23 +00:00
Matteo Pagliazzi 99f50f825a Old Client Cleanup (#9141)
* move emails images to website/static/emails and remove old files

* remove old client tests

* remove more files

* add sprites back

* cleanup gulp

* cleanup gulp

* remove old files

* more fixes

* pin bootstrap-vue

* disable old test

* remove old tasks

* fix apidoc
2017-10-12 16:44:28 +02:00
Sabe Jones 2308f2397e 4.3.0 2017-10-10 22:55:47 +00:00
Sabe Jones f10d5110e5 Merge branch 'develop' into release 2017-10-10 22:55:39 +00:00
Sabe Jones 5ced47f590 Content 2017/10/10 (#9177)
* feat(content): bundle, customizations

* chore(sprites): update
2017-10-10 17:30:44 -05:00
Sabe Jones 24b2bc9aa1 4.2.3 2017-10-10 17:12:43 +00:00
Sabe Jones aea08a971a Merge branch 'develop' into release 2017-10-10 17:12:35 +00:00
Sabe Jones e3452b3ba7 chore(i18n): update locales 2017-10-10 17:11:48 +00:00
Michael Hibbs 3db666eb9e Show error message in empty market inventory drawer (#9041)
* client/tasks/user: (create dropdown) change icon color on menuitem hover

Previously the color was only changing when you hovered over the
text or icon.

This change changes the icon color when you hover anywhere over
the `menuitem`

* client/shops/market: fix search field in market

* Hide drawer sliders if number of items for type is empty
* Move drawer-help-text icon down to line up vertically with text
* Add TODO for error thrown when trying to sell special item types

* client/tasks/taskModal: display all selected tags for task

* i18n/groups: religous to religious

* client/shops/market: Add error message to empty drawer slider

* Create new locale file to hold inventory locales

* client/tasks/user: Revert change to create dropdown

* Revert tag changes

This will be picked up in a separate PR.

* Fix missing space before function paren
2017-10-10 11:18:50 -05:00
negue cf5985e38c oct 9 fixes (#9175)
* hide hatching modal before opening the hatched modal

* fix Options not sticking: Costume, Auto Equip

* Hall of Heroes - Collapse Items & Auth

* prevent wrapping currency icon+text

* fix spacing of remaining gems (if two digits)

* fix logo in safari (removed <mask />)

* update seasonal shop items on purchase
2017-10-09 22:40:39 -05:00
Sabe Jones d88a8247d1 Fixes 2017/10/09 (#9176)
* fix(quests): correctly divide owner-only/all

* fix(equipment): clarify locked items
2017-10-09 22:15:52 -05:00
Keith Holliday 7e1ae6a571 Oct 9 fixes (#9174)
* Prevented purchasing of locked quest

* Fixed category filters for challenges

* Added armoire equipment directly to user

* Halfed the time notifications display

* Added quest accept/reject to party page
2017-10-09 14:40:45 -05:00
Sabe Jones d117117885 4.2.2 2017-10-09 19:17:49 +00:00
Sabe Jones 5dd168eee4 chore(i18n): update locales 2017-10-09 19:14:21 +00:00
Keith Holliday a7c9355dd5 Fixed breaking api change (#9170) 2017-10-06 16:06:48 -05:00
Matteo Pagliazzi 81d3e8a68f add extra logging for ios/verify 2017-10-06 18:07:26 +02:00
Sabe Jones e0fbfffbf2 4.2.1 2017-10-06 00:49:02 +00:00
Sabe Jones 36b968a74a fix(news): remove Last Chance blurb 2017-10-06 00:48:34 +00:00
Sabe Jones fff249fb00 4.2.0 2017-10-06 00:39:47 +00:00
Sabe Jones ce5c6b9517 Merge branch 'develop' into release 2017-10-06 00:39:40 +00:00
Sabe Jones 702013f9ed chore(i18n): update locales 2017-10-06 00:38:10 +00:00
Sabe Jones 3503b307b2 Content and fixes 2017-10-05 (#9165)
* fix(market): display classless equipment

* fix(challenges): more missing Markdown

* feat(content): backgrounds and Enchanted Armoire 2017-10

* chore(sprites): compile
2017-10-05 19:00:39 -05:00
Keith Holliday f1ec8bbf2c Added styles to selecting challenge winner (#9164) 2017-10-05 18:02:26 -05:00
Keith Holliday 58b033db9e Oct 5 fixes (#9163)
* Added removing invites

* Addeed messages for empty gems to icon

* Added member for challenge members

* Fixed task cloning ending

* Fixed group task assignment

* Added small hack to prevent scrolling issues

* Fixed lint
2017-10-05 14:41:03 -05:00
8070 changed files with 38274 additions and 123648 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"directory": "website/client-old/bower_components"
}
+2 -6
View File
@@ -20,11 +20,7 @@ container_commands:
command: "touch /tmp/.babel.json"
02_ownBabel:
command: "chmod a+rw /tmp/.babel.json"
03_installBower:
command: "$NODE_HOME/bin/npm install -g bower"
04_installGulp:
03_installGulp:
command: "$NODE_HOME/bin/npm install -g gulp"
05_runBower:
command: "$NODE_HOME/lib/node_modules/bower/bin/bower --config.interactive=false --allow-root install -f"
06_runGulp:
04_runGulp:
command: "$NODE_HOME/lib/node_modules/gulp/bin/gulp.js build"
-2
View File
@@ -16,5 +16,3 @@ migrations/*
scripts/*
website/common/browserify.js
Gruntfile.js
gulpfile.js
gulp
+1 -1
View File
@@ -4,7 +4,7 @@
# Pull Request
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request)
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
# Requesting a feature
+1 -1
View File
@@ -6,7 +6,7 @@
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
[//]: # (Fill out relevant information - UUID is found from the Habitia website at User Icon > Settings > API)
### General Info
* UUID:
* Browser:
+2 -2
View File
@@ -1,4 +1,4 @@
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request for more info)
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
Fixes put_issue_url_here
@@ -8,7 +8,7 @@ Fixes put_issue_url_here
[//]: # (Put User ID in here - found in Settings -> API)
[//]: # (Put User ID in here - found on the Habitica website at User Icon > Settings > API)
----
UUID:
+3
View File
@@ -2,11 +2,14 @@
website/client-old/gen
website/client-old/common
website/client-old/apidoc
website/build
website/client-old/js/habitrpg-shared.js*
website/client-old/css/habitrpg-shared.css
website/transpiled-babel/
website/common/transpiled-babel/
node_modules
content_cache
apidoc_build
*.swp
.idea*
config.json
-1
View File
@@ -34,5 +34,4 @@ env:
- TEST="test:sanity"
- TEST="test:content" COVERAGE=true
- TEST="test:common" COVERAGE=true
- TEST="client:unit" COVERAGE=true
- TEST="apidoc"
+1 -2
View File
@@ -4,7 +4,7 @@ FROM node:boron
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
RUN yarn global add npm@5
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
RUN npm install -g gulp mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
@@ -12,7 +12,6 @@ WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN cp config.json.example config.json
RUN npm install
RUN bower install --allow-root
# Create Build dir
RUN mkdir -p ./website/build
+13 -3
View File
@@ -1,17 +1,27 @@
FROM node:boron
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
RUN yarn global add npm@5
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
RUN npm install -g gulp mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.0.3 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.10.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
RUN gulp build:prod --force
# Create Build dir
-142
View File
@@ -1,142 +0,0 @@
/*global module:false*/
require('babel-register');
var _ = require('lodash');
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
karma: {
unit: {
configFile: 'test/client-old/spec/karma.conf.js'
},
continuous: {
configFile: 'test/client-old/spec/karma.conf.js',
singleRun: true,
autoWatch: false
}
},
clean: {
build: ['website/build']
},
cssmin: {
dist: {
options: {
report: 'gzip'
},
files:{
"website/client-old/css/habitrpg-shared.css": [
"website/assets/sprites/dist/spritesmith*.css",
"website/assets/sprites/css/backer.css",
"website/assets/sprites/css/Mounts.css",
"website/assets/sprites/css/index.css"
]
}
}
},
stylus: {
build: {
options: {
compress: false, // AFTER
'include css': true,
paths: ['website/client-old']
},
files: {
'website/build/app.css': ['website/client-old/css/index.styl'],
'website/build/static.css': ['website/client-old/css/static.styl']
}
}
},
copy: {
build: {
files: [
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
]
}
},
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
hashres: {
build: {
options: {
fileNameFormat: '${name}-${hash}.${ext}'
},
src: [
'website/build/*.js',
'website/build/*.css',
'website/build/favicon.ico',
'website/build/favicon_192x192.png',
'website/build/*.png',
'website/build/static/sprites/*.png',
'website/build/*.gif',
'website/build/bower_components/bootstrap/dist/fonts/*'
],
dest: 'website/build/*.css'
}
}
});
//Load build files from client-old/manifest.json
grunt.registerTask('loadManifestFiles', 'Load all build files from client-old/manifest.json', function(){
var files = grunt.file.readJSON('./website/client-old/manifest.json');
var uglify = {};
var cssmin = {};
_.each(files, function(val, key){
var js = uglify['website/build/' + key + '.js'] = [];
_.each(files[key].js, function(val){
var path = "./";
if( val.indexOf('common/') == -1)
path = './website/client-old/';
js.push(path + val);
});
var css = cssmin['website/build/' + key + '.css'] = [];
_.each(files[key].css, function(val){
var path = "./";
if( val.indexOf('common/') == -1) {
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client-old/';
}
css.push(path + val)
});
});
grunt.config.set('uglify.build.files', uglify);
grunt.config.set('uglify.build.options', {compress: false});
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'website/client-old/css/whatever-css.css'});
});
// Register tasks.
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
grunt.registerTask('build:dev', ['cssmin', 'stylus']);
grunt.registerTask('build:test', ['build:dev']);
// Load tasks
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-stylus');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-hashres');
if (process.env.NODE_ENV !== 'production') grunt.loadNpmTasks('grunt-karma');
};
-56
View File
@@ -1,56 +0,0 @@
{
"name": "HabitRPG",
"version": "0.1.1",
"homepage": "https://github.com/lefnire/habitrpg",
"authors": [
"Tyler Renelle <tylerrenelle@gmail.com>"
],
"private": true,
"ignore": [
"**/.*",
"node_modules",
"website/client-old/bower_components",
"test",
"tests"
],
"dependencies": {
"Angular-At-Directive": "snicker/Angular-At-Directive#c27bae207aa06d1e",
"angular": "1.3.9",
"angular-bootstrap": "0.13.0",
"angular-filter": "0.5.1",
"angular-loading-bar": "0.6.0",
"angular-resource": "1.3.9",
"angular-sanitize": "1.3.9",
"angular-ui": "0.4.0",
"angular-ui-router": "0.2.13",
"angular-ui-select2": "angular-ui/ui-select2#afa6589a54cb72815f",
"angular-ui-utils": "0.1.0",
"bootstrap": "3.1.0",
"bootstrap-growl": "ifightcrime/bootstrap-growl#162daa41cd1155f",
"bootstrap-tour": "0.10.1",
"css-social-buttons": "samcollins/css-social-buttons#v1.1.1 ",
"github-buttons": "mdo/github-buttons#v3.0.0",
"hello": "1.14.1",
"jquery": "2.1.0",
"jquery-colorbox": "1.4.36",
"jquery-ui": "1.10.3",
"jquery.cookie": "1.4.0",
"js-emoji": "snicker/js-emoji#f25d8a303f",
"ngInfiniteScroll": "1.1.0",
"pnotify": "1.3.1",
"sticky": "1.0.3",
"swagger-ui": "wordnik/swagger-ui#v2.0.24",
"smart-app-banner": "78ef9c0679723b25be1a0ae04f7b4aef7cbced4f",
"habitica-markdown": "1.2.2",
"pusher-js-auth": "^2.0.0",
"pusher-websocket-iso": "pusher#^3.2.0",
"taggle": "^1.11.1"
},
"devDependencies": {
"angular-mocks": "1.3.9"
},
"resolutions": {
"angular": "1.3.9",
"jquery": ">=1.9.0"
}
}
-10
View File
@@ -1,10 +0,0 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg/server",
"habitrpg/babel"
],
}
+2 -2
View File
@@ -2,7 +2,7 @@ import gulp from 'gulp';
import clean from 'rimraf';
import apidoc from 'apidoc';
const APIDOC_DEST_PATH = './website/build/apidoc';
const APIDOC_DEST_PATH = './apidoc_build';
const APIDOC_SRC_PATH = './website/server';
gulp.task('apidoc:clean', (done) => {
clean(APIDOC_DEST_PATH, done);
@@ -22,5 +22,5 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
});
gulp.task('apidoc:watch', ['apidoc'], () => {
return gulp.watch(APIDOC_SRC_PATH + '/**/*.js', ['apidoc']);
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
});
-31
View File
@@ -1,31 +0,0 @@
import gulp from 'gulp';
import browserify from 'browserify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import babel from 'babelify';
gulp.task('browserify', function () {
let bundler = browserify({
entries: './website/common/browserify.js',
debug: true,
transform: [[babel, { compact: false }]],
});
return bundler.bundle()
.pipe(source('habitrpg-shared.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify())
.on('error', function (err) {
console.error(err);
this.emit('end');
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./website/client-old/js/'));
});
gulp.task('browserify:watch', () => {
gulp.watch('./website/common/script/**/*.js', ['browserify']);
});
+3 -3
View File
@@ -8,10 +8,10 @@ const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.sc
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
// https://stackoverflow.com/a/14387791/969528
function copyFile(source, target, cb) {
function copyFile (source, target, cb) {
let cbCalled = false;
function done(err) {
function done (err) {
if (!cbCalled) {
cb(err);
cbCalled = true;
@@ -33,4 +33,4 @@ gulp.task('bootstrap', (done) => {
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
done,
);
});
});
+3 -17
View File
@@ -1,14 +1,10 @@
import gulp from 'gulp';
import runSequence from 'run-sequence';
import babel from 'gulp-babel';
import webpackProductionBuild from '../webpack/build';
require('gulp-grunt')(gulp);
gulp.task('build', () => {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
gulp.start('build:prod');
} else {
gulp.start('build:dev');
}
});
@@ -30,22 +26,12 @@ gulp.task('build:server', ['build:src', 'build:common']);
gulp.task('build:client', ['bootstrap'], (done) => {
webpackProductionBuild((err, output) => {
if (err) return done(err);
console.log(output);
console.log(output); // eslint-disable-line no-console
});
});
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
gulp.start('grunt-build:dev', done);
});
gulp.task('build:dev:watch', ['build:dev'], () => {
gulp.watch(['website/client-old/**/*.styl', 'website/common/script/*']);
});
gulp.task('build:prod', [
'browserify',
'build:server',
'prepare:staticNewStuff',
'build:server',
'build:client',
'apidoc',
]);
+10 -10
View File
@@ -7,10 +7,11 @@ import gulp from 'gulp';
// Add additional properties to the repl's context
let improveRepl = (context) => {
// Let "exit" and "quit" terminate the console
['exit', 'quit'].forEach((term) => {
Object.defineProperty(context, term, { get () { process.exit(); }});
Object.defineProperty(context, term, { get () {
process.exit();
}});
});
// "clear" clears the screen
@@ -18,12 +19,12 @@ let improveRepl = (context) => {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
context.Challenge = require('../website/server/models/challenge').model;
context.Group = require('../website/server/models/group').model;
context.User = require('../website/server/models/user').model;
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
var isProd = nconf.get('NODE_ENV') === 'production';
var mongooseOptions = !isProd ? {} : {
const isProd = nconf.get('NODE_ENV') === 'production';
const mongooseOptions = !isProd ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
@@ -31,16 +32,15 @@ let improveRepl = (context) => {
mongoose.connect(
nconf.get('NODE_DB_URI'),
mongooseOptions,
function (err) {
(err) => {
if (err) throw err;
logger.info('Connected with Mongoose');
}
)
);
};
gulp.task('console', (cb) => {
gulp.task('console', () => {
improveRepl(repl.start({
prompt: 'Habitica > ',
}).context);
-10
View File
@@ -1,10 +0,0 @@
import gulp from 'gulp';
import jade from 'jade';
import {writeFileSync} from 'fs';
gulp.task('prepare:staticNewStuff', () => {
writeFileSync(
'./website/client-old/new-stuff.html',
jade.compileFile('./website/views/shared/new-stuff.jade')()
);
});
+105 -104
View File
@@ -10,82 +10,39 @@ import {each} from 'lodash';
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
const DIST_PATH = 'website/assets/sprites/dist/';
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
const IMG_DIST_PATH = 'website/static/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
gulp.task('sprites:main', () => {
let mainSrc = sync('website/assets/sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/assets/sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions');
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${DIST_PATH}*.png`);
each(distSpritesheets, (img, index) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`);
}
});
if (numberOfSheetsThatAreTooBig > 0) {
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
} else {
console.log('All images are within the correct dimensions');
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
}
});
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
let padding = 0;
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
if (addPadding) {
padding = dims.width * 8 + dims.height * 8;
}
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
cssVarMap: cssVarMap,
}));
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
let totalPixelSize = dims.width * dims.height + padding;
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
return totalPixelSize;
}
function calculateSpritesheetsSrcIndicies (src) {
@@ -105,37 +62,6 @@ function calculateSpritesheetsSrcIndicies (src) {
return slices;
}
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
}
let padding = 0;
if (addPadding) {
padding = (dims.width * 8) + (dims.height * 8);
}
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
let totalPixelSize = (dims.width * dims.height) + padding;
return totalPixelSize;
}
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
function cssVarMap (sprite) {
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
// 60x60 image pointing at the proper part of the 90x90 sprite.
@@ -144,18 +70,93 @@ function cssVarMap (sprite) {
if (requiresSpecialTreatment) {
sprite.custom = {
px: {
offset_x: `-${ sprite.x + 25 }px`,
offset_y: `-${ sprite.y + 15 }px`,
offsetX: `-${ sprite.x + 25 }px`,
offsetY: `-${ sprite.y + 15 }px`,
width: '60px',
height: '60px',
},
};
}
if (~sprite.name.indexOf('shirt'))
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
if (~sprite.name.indexOf('hair_base')) {
let styleArray = sprite.name.split('_').slice(2,3);
if (sprite.name.indexOf('shirt') !== -1)
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
if (sprite.name.indexOf('hair_base') !== -1) {
let styleArray = sprite.name.split('_').slice(2, 3);
if (Number(styleArray[0]) > 14)
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
}
}
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
cssVarMap,
}));
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH));
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
}
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
gulp.task('sprites:main', () => {
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
}
});
if (numberOfSheetsThatAreTooBig > 0) {
// https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
console.error( // eslint-disable-line no-console
`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle
them, but there is a margin of error in these calculations so it is probably okay. Mention
this to an admin so they can test a staging site on mobile Safari after your PR is merged.`);
} else {
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
}
});
-2
View File
@@ -3,8 +3,6 @@ import nodemon from 'gulp-nodemon';
let pkg = require('../package.json');
gulp.task('run:dev', ['nodemon', 'build:dev:watch']);
gulp.task('nodemon', () => {
nodemon({
script: pkg.main,
+21 -136
View File
@@ -1,21 +1,12 @@
import {
pipe,
awaitPort,
kill,
runMochaTests,
} from './taskHelper';
import { server as karma } from 'karma';
import mongoose from 'mongoose';
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
import Bluebird from 'bluebird';
import runSequence from 'run-sequence';
import os from 'os';
import nconf from 'nconf';
import fs from 'fs';
const i18n = require('../website/server/libs/i18n');
// TODO rewrite
@@ -24,25 +15,23 @@ let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI');
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
const SANITY_TEST_COMMAND = 'npm run test:sanity';
const COMMON_TEST_COMMAND = 'npm run test:common';
const CONTENT_TEST_COMMAND = 'npm run test:content';
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
const KARMA_TEST_COMMAND = 'npm run test:karma';
/* Helper methods for reporting test summary */
let testResults = [];
let testCount = (stdout, regexp) => {
let match = stdout.match(regexp);
return parseInt(match && match[1] || 0);
return parseInt(match && match[1] || 0, 10);
};
let testBin = (string, additionalEnvVariables = '') => {
if (os.platform() === 'win32') {
if (additionalEnvVariables != '') {
if (additionalEnvVariables !== '') {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
additionalEnvVariables = `set ${additionalEnvVariables}&&`;
}
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
} else {
@@ -50,9 +39,9 @@ let testBin = (string, additionalEnvVariables = '') => {
}
};
gulp.task('test:nodemon', (done) => {
process.env.PORT = TEST_SERVER_PORT;
process.env.NODE_DB_URI = TEST_DB_URI;
gulp.task('test:nodemon', () => {
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
runSequence('nodemon');
});
@@ -69,37 +58,27 @@ gulp.task('test:prepare:mongo', (cb) => {
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
if (error) {
throw new Error(`Problem with the server: ${error}`);
}
if (stderr) {
console.error(stderr); // eslint-disable-line no-console
}
});
}
});
gulp.task('test:prepare:translations', (cb) => {
fs.writeFile(
'test/client-old/spec/mocks/translations.js',
`if(!window.env) window.env = {};
window.env.translations = ${JSON.stringify(i18n.translations['en'])};`, cb);
});
gulp.task('test:prepare:build', ['build', 'test:prepare:translations']);
// exec(testBin('grunt build:test'), cb);
gulp.task('test:prepare:webdriver', (cb) => {
exec('npm run test:prepare:webdriver', cb);
});
gulp.task('test:prepare:build', ['build']);
gulp.task('test:prepare', [
'test:prepare:build',
'test:prepare:mongo',
'test:prepare:webdriver',
]);
gulp.task('test:sanity', (cb) => {
let runner = exec(
testBin(SANITY_TEST_COMMAND),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -112,7 +91,7 @@ gulp.task('test:sanity', (cb) => {
gulp.task('test:common', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -133,7 +112,7 @@ gulp.task('test:common:watch', ['test:common:clean'], () => {
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err, stdout, stderr) => {
(err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({
suite: 'Common Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -150,7 +129,7 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -172,7 +151,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err, stdout, stderr) => {
(err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({
suite: 'Content Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -185,103 +164,10 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
pipe(runner);
});
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(KARMA_TEST_COMMAND),
(err, stdout) => {
if (err) {
process.exit(1);
}
cb();
}
);
pipe(runner);
});
gulp.task('test:karma:watch', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(`${KARMA_TEST_COMMAND}:watch`),
(err, stdout) => {
cb(err);
}
);
pipe(runner);
});
gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(KARMA_TEST_COMMAND),
(err, stdout) => {
testResults.push({
suite: 'Karma Specs\t',
pass: testCount(stdout, /(\d+) tests? completed/),
fail: testCount(stdout, /(\d+) tests? failed/),
pend: testCount(stdout, /(\d+) tests? skipped/),
});
cb();
}
);
pipe(runner);
});
gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
testBin('npm run test:e2e:webdriver', 'DISPLAY=:99'),
].map(exec);
support.push(server);
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
(err, stdout, stderr) => {
support.forEach(kill);
if (err) {
process.exit(1);
}
cb();
}
);
pipe(runner);
});
});
gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
'npm run test:e2e:webdriver',
].map(exec);
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
(err, stdout, stderr) => {
let match = stdout.match(/(\d+) tests?.*(\d) failures?/);
testResults.push({
suite: 'End-to-End Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/),
});
support.forEach(kill);
cb();
}
);
pipe(runner);
});
});
gulp.task('test:api-v3:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -300,7 +186,7 @@ gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -320,7 +206,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => done(err)
(err) => done(err)
);
pipe(runner);
@@ -331,7 +217,6 @@ gulp.task('test', (done) => {
'test:sanity',
'test:content',
'test:common',
'test:karma',
'test:api-v3:unit',
'test:api-v3:integration',
done
+81 -84
View File
@@ -1,6 +1,5 @@
import fs from 'fs';
import _ from 'lodash';
import nconf from 'nconf';
import gulp from 'gulp';
import { postToSlack, conf } from './taskHelper';
@@ -12,8 +11,82 @@ const SLACK_CONFIG = {
const LOCALES = './website/common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`;
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
const ALL_LANGUAGES = getArrayOfLanguages();
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
let parsedTranslationFile;
try {
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
const translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
let strings = {};
_.each(json, (fileName) => {
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
const parsedJson = JSON.parse(rawFile);
strings[fileName] = {};
_.each(parsedJson, (value, key) => {
const match = value.match(interpolationRegex);
if (match) strings[fileName][key] = match;
});
});
return strings;
}
const malformedStringExceptions = {
messageDropFood: true,
armoireFood: true,
@@ -23,7 +96,6 @@ const malformedStringExceptions = {
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
gulp.task('transifex:missingFiles', () => {
let missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => {
@@ -40,7 +112,6 @@ gulp.task('transifex:missingFiles', () => {
});
gulp.task('transifex:missingStrings', () => {
let missingStrings = [];
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
@@ -58,7 +129,6 @@ gulp.task('transifex:missingStrings', () => {
});
gulp.task('transifex:malformedStrings', () => {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
@@ -66,25 +136,23 @@ gulp.task('transifex:malformedStrings', () => {
let stringsWithMalformedInterpolations = [];
let stringsWithIncorrectNumberOfInterpolations = [];
let count = 0;
_.each(ALL_LANGUAGES, function (lang) {
_.each(stringsToLookFor, function (strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
_.each(ALL_LANGUAGES, (lang) => {
_.each(stringsToLookFor, (strings, filename) => {
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
let parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, function (value, key) {
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
let translationString = parsedTranslationFile[key];
if (!translationString) return;
let englishOccurences = stringsToLookFor[file][key];
let englishOccurences = stringsToLookFor[filename][key];
let translationOccurences = translationString.match(interpolationRegex);
if (!translationOccurences) {
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
}
});
@@ -103,74 +171,3 @@ gulp.task('transifex:malformedStrings', () => {
postToSlack(formattedMessage, SLACK_CONFIG);
}
});
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
try {
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
var parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
var translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
var strings = {};
_.each(json, function (file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function (value, key) {
var match = value.match(interpolationRegex);
if (match) strings[file_name][key] = match;
});
});
return strings;
}
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}
+29 -20
View File
@@ -12,7 +12,7 @@ import { resolve } from 'path';
* Get access to configruable values
*/
nconf.argv().env().file({ file: 'config.json' });
export var conf = nconf;
export const conf = nconf;
/*
* Kill a child process and any sub-children that process may have spawned.
@@ -26,11 +26,12 @@ export function kill (proc) {
pids.forEach(kill); return;
}
try {
exec(/^win/.test(process.platform)
? `taskkill /PID ${pid} /T /F`
: `kill -9 ${pid}`);
exec(/^win/.test(process.platform) ?
`taskkill /PID ${pid} /T /F` :
`kill -9 ${pid}`);
} catch (e) {
console.log(e); // eslint-disable-line no-console
}
catch (e) { console.log(e); }
});
};
@@ -44,21 +45,25 @@ export function kill (proc) {
* before failing.
*/
export function awaitPort (port, max = 60) {
return new Bluebird((reject, resolve) => {
let socket, timeout, interval;
return new Bluebird((rej, res) => {
let socket;
let timeout;
let interval;
timeout = setTimeout(() => {
clearInterval(interval);
reject(`Timed out after ${max} seconds`);
rej(`Timed out after ${max} seconds`);
}, max * 1000);
interval = setInterval(() => {
socket = net.connect({port: port}, () => {
socket = net.connect({port}, () => {
clearInterval(interval);
clearTimeout(timeout);
socket.destroy();
resolve();
}).on('error', () => { socket.destroy; });
res();
}).on('error', () => {
socket.destroy();
});
}, 1000);
});
}
@@ -67,8 +72,12 @@ export function awaitPort (port, max = 60) {
* Pipe the child's stdin and stderr to the parent process.
*/
export function pipe (child) {
child.stdout.on('data', (data) => { process.stdout.write(data); });
child.stderr.on('data', (data) => { process.stderr.write(data); });
child.stdout.on('data', (data) => {
process.stdout.write(data);
});
child.stderr.on('data', (data) => {
process.stderr.write(data);
});
}
/*
@@ -78,8 +87,8 @@ export function postToSlack (msg, config = {}) {
let slackUrl = nconf.get('SLACK_URL');
if (!slackUrl) {
console.error('No slack post url specified. Your message was:');
console.log(msg);
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
console.log(msg); // eslint-disable-line no-console
return;
}
@@ -89,15 +98,15 @@ export function postToSlack (msg, config = {}) {
channel: `#${config.channel || '#general'}`,
username: config.username || 'gulp task',
text: msg,
icon_emoji: `:${config.emoji || 'gulp'}:`,
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
})
.end((err, res) => {
if (err) console.error('Unable to post to slack', err);
.end((err) => {
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
});
}
export function runMochaTests (files, server, cb) {
require('../test/helpers/globals.helper');
require('../test/helpers/globals.helper'); // eslint-disable-line global-require
let mocha = new Mocha({reporter: 'spec'});
let tests = glob(files);
@@ -108,7 +117,7 @@ export function runMochaTests (files, server, cb) {
});
mocha.run((numberOfFailures) => {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
if (server) kill(server);
process.exit(numberOfFailures);
}
+6 -8
View File
@@ -8,13 +8,11 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') {
require('./gulp/gulp-apidoc');
require('./gulp/gulp-newstuff');
require('./gulp/gulp-build');
require('./gulp/gulp-babelify');
require('./gulp/gulp-bootstrap');
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
require('./gulp/gulp-build'); // eslint-disable-line global-require
require('./gulp/gulp-bootstrap'); // eslint-disable-line global-require
} else {
require('glob').sync('./gulp/gulp-*').forEach(require);
require('gulp').task('default', ['test']);
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
require('gulp').task('default', ['test']); // eslint-disable-line global-require
}
+111
View File
@@ -0,0 +1,111 @@
var migrationName = '20171030_jackolanterns.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award the Jack-O'-Lantern ladder:
* Ghost Jack-O-Lantern Mount to owners of Ghost Jack-O-Lantern Pet
* Ghost Jack-O-Lantern Pet to owners of Jack-O-Lantern Mount
* Jack-O-Lantern Mount to owners of Jack-O-Lantern Pet
* Jack-O-Lantern Pet to everyone else
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.pets',
'items.mounts',
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var set = {};
var inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Ghost': true};
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost': 5};
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base': true};
} else {
set = {'migration':migrationName, 'items.pets.JackOLantern-Base': 5};
}
dbUsers.update({_id: user._id}, {$set:set, $inc:inc});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
+1 -1
View File
@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['shield_mystery_201709','back_mystery_201709']
$each:['armor_mystery_201710','head_mystery_201710']
}
}
};
+11 -1
View File
@@ -65,19 +65,29 @@ function updateUser (user) {
set = {'migration':migrationName};
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', '_id': monk.id()}};
} else {
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', '_id': monk.id()}};
}
dbUsers.update({_id: user._id}, {$set:set});
if (push) {
dbUsers.update({_id: user._id}, {$set: set, $push: push});
} else {
dbUsers.update({_id: user._id}, {$set: set});
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
+2368 -4416
View File
File diff suppressed because it is too large Load Diff
+8 -25
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.1.4",
"version": "4.10.1",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -32,8 +32,7 @@
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "4.0.0-alpha.6",
"bootstrap-vue": "^1.0.0-beta.6",
"bower": "~1.3.12",
"bootstrap-vue": "1.0.0-beta.7",
"browserify": "~12.0.1",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
@@ -53,18 +52,8 @@
"file-loader": "^0.10.0",
"glob": "^4.3.5",
"got": "^6.1.1",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-contrib-clean": "~0.6.0",
"grunt-contrib-copy": "~0.6.0",
"grunt-contrib-cssmin": "~0.10.0",
"grunt-contrib-stylus": "~0.20.0",
"grunt-contrib-uglify": "~0.6.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-hashres": "git://github.com/habitrpg/grunt-hashres#dc85db6d3002e29e1b7c5ee186b80d708d2f0e0b",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
"gulp-grunt": "^0.5.2",
"gulp-imagemin": "^2.4.0",
"gulp-nodemon": "^2.0.4",
"gulp-sourcemaps": "^1.6.0",
@@ -127,12 +116,12 @@
"validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.1.0",
"vue-loader": "^11.0.0",
"vue": "^2.5.2",
"vue-loader": "^13.3.0",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^2.0.0-rc.5",
"vue-router": "^3.0.0",
"vue-style-loader": "^3.0.0",
"vue-template-compiler": "^2.1.10",
"vue-template-compiler": "^2.5.2",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#45e607a7bccf4e3e089761b3b7b33e3f2c5dc21f",
"webpack": "^2.2.1",
"webpack-merge": "^4.0.0",
@@ -147,7 +136,7 @@
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
"test": "npm run lint && gulp test && gulp apidoc",
"test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3",
"test:api-v3:unit": "gulp test:api-v3:unit",
@@ -156,11 +145,6 @@
"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:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
"test:prepare:webdriver": "webdriver-manager update",
"test:e2e:webdriver": "webdriver-manager start",
"test:e2e": "protractor test/client-old/e2e/protractor.conf.js",
"test:nodemon": "gulp test:nodemon",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"sprites": "gulp sprites:compile",
@@ -170,7 +154,7 @@
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
"client:e2e": "node test/client/e2e/runner.js",
"client:test": "npm run client:unit && npm run client:e2e",
"start": "gulp run:dev",
"start": "gulp nodemon",
"postinstall": "gulp build",
"apidoc": "gulp apidoc"
},
@@ -194,7 +178,6 @@
"event-stream": "^3.2.2",
"eventsource-polyfill": "^0.9.6",
"expect.js": "~0.2.0",
"grunt-karma": "~0.12.1",
"http-proxy-middleware": "^0.17.0",
"inject-loader": "^3.0.0-beta4",
"istanbul": "^1.1.0-alpha.1",
@@ -142,4 +142,22 @@ describe('GET /challenges/:challengeId/members', () => {
let resIds = res.concat(res2).map(member => member._id);
expect(resIds).to.eql(expectedIds.sort());
});
it('supports using req.query.search to get search members', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let challenge = await generateChallenge(user, group);
let usersToGenerate = [];
for (let i = 0; i < 3; i++) {
usersToGenerate.push(generateUser({challenges: [challenge._id]}));
}
let generatedUsers = await Promise.all(usersToGenerate);
let profileNames = generatedUsers.map(generatedUser => generatedUser.profile.name);
let firstProfileName = profileNames[0];
let nameToSearch = firstProfileName.substring(0, 4);
let response = await user.get(`/challenges/${challenge._id}/members?search=${nameToSearch}`);
expect(response[0].profile.name).to.eql(firstProfileName);
});
});
@@ -4,6 +4,7 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { TAVERN_ID } from '../../../../../website/common/script/constants';
describe('GET challenges/groups/:groupId', () => {
context('Public Guild', () => {
@@ -181,4 +182,123 @@ describe('GET challenges/groups/:groupId', () => {
expect(foundChallengeIndex).to.eql(1);
});
});
context('Party', () => {
let party, user, nonMember, challenge, challenge2;
before(async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestParty',
type: 'party',
},
});
party = group;
user = groupLeader;
nonMember = await generateUser();
challenge = await generateChallenge(user, group);
challenge2 = await generateChallenge(user, group);
});
it('should prevent non-member from seeing challenges', async () => {
await expect(nonMember.get(`/challenges/groups/${party._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('should return group challenges for member with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${party._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
it('should return group challenges for member using ID "party"', async () => {
let challenges = await user.get('/challenges/groups/party');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
});
context('Tavern', () => {
let tavern, user, challenge, challenge2;
before(async () => {
user = await generateUser();
await user.update({balance: 0.5});
tavern = await user.get(`/groups/${TAVERN_ID}`);
challenge = await generateChallenge(user, tavern, {prize: 1});
challenge2 = await generateChallenge(user, tavern, {prize: 1});
});
it('should return tavern challenges with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${TAVERN_ID}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
it('should return tavern challenges using ID "habitrpg', async () => {
let challenges = await user.get('/challenges/groups/habitrpg');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
});
});
@@ -4,7 +4,7 @@ import {
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('GET /export/avatar-:memberId.html', () => {
xdescribe('GET /export/avatar-:memberId.html', () => {
let user;
before(async () => {
@@ -98,4 +98,24 @@ describe('POST /user/purchase/:type/:key', () => {
await members[0].sync();
expect(members[0].balance).to.equal(oldBalance);
});
describe('bulk purchasing', () => {
it('purchases a gem item', async () => {
await user.post(`/user/purchase/${type}/${key}`, {quantity: 2});
await user.sync();
expect(user.items[type][key]).to.equal(2);
});
it('can convert gold to gems if subscribed', async () => {
let oldBalance = user.balance;
await user.update({
'purchased.plan.customerId': 'group-plan',
'stats.gp': 1000,
});
await user.post('/user/purchase/gems/gem', {quantity: 2});
await user.sync();
expect(user.balance).to.equal(oldBalance + 0.50);
});
});
});
@@ -3,8 +3,8 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../website/common/script';
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
let content = shared.content;
@@ -82,4 +82,19 @@ describe('POST /user/buy/:key', () => {
itemText: item.text(),
}));
});
it('allows for bulk purchases', async () => {
await user.update({
'stats.gp': 400,
'stats.hp': 20,
});
let potion = content.potion;
let res = await user.post('/user/buy/potion', {quantity: 2});
await user.sync();
expect(user.stats.hp).to.equal(50);
expect(res.data).to.eql(user.stats);
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
});
});
@@ -1,7 +1,7 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/buy-armoire', () => {
let user;
@@ -3,7 +3,7 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/buy-gear/:key', () => {
let user;
@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../website/common/script';
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
let content = shared.content;
@@ -1,7 +1,7 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/buy-mystery-set/:key', () => {
let user;
@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../website/common/script';
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
let content = shared.content;
@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../website/common/script';
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
let content = shared.content;
@@ -1,7 +1,7 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate', () => {
let user;
@@ -0,0 +1,40 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate-bulk', () => {
let user;
const statsUpdate = {
stats: {
con: 1,
str: 2,
},
};
beforeEach(async () => {
user = await generateUser();
});
// More tests in common code unit tests
it('returns an error if user does not have enough points', async () => {
await expect(user.post('/user/allocate-bulk', statsUpdate))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('notEnoughAttrPoints'),
});
});
it('allocates attribute points', async () => {
await user.update({'stats.points': 3});
await user.post('/user/allocate-bulk', statsUpdate);
await user.sync();
expect(user.stats.con).to.equal(1);
expect(user.stats.str).to.equal(2);
expect(user.stats.points).to.equal(0);
});
});
@@ -1,6 +1,6 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate-now', () => {
// More tests in common code unit tests
@@ -1,31 +0,0 @@
import {
getManifestFiles,
} from '../../../../../website/server/libs/buildManifest';
describe('Build Manifest', () => {
describe('getManifestFiles', () => {
it('returns an html string', () => {
let htmlCode = getManifestFiles('app');
expect(htmlCode.startsWith('<script') || htmlCode.startsWith('<link')).to.be.true;
});
it('can return only js files', () => {
let htmlCode = getManifestFiles('app', 'js');
expect(htmlCode.indexOf('<link') === -1).to.be.true;
});
it('can return only css files', () => {
let htmlCode = getManifestFiles('app', 'css');
expect(htmlCode.indexOf('<script') === -1).to.be.true;
});
it('throws an error in case the page does not exist', () => {
expect(() => {
getManifestFiles('strange name here');
}).to.throw(Error);
});
});
});
+66
View File
@@ -365,6 +365,72 @@ describe('cron', () => {
expect(user.history.todos).to.be.lengthOf(1);
});
it('should remove completed todos from users taskOrder list', () => {
tasksByType.todos = [];
user.tasksOrder.todos = [];
let todo = {
text: 'test todo',
type: 'todo',
value: 0,
};
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
tasksByType.todos[0].completed = true;
user.tasksOrder.todos = tasksByType.todos.map(taskTodo => {
return taskTodo._id;
});
// Since ideally tasksByType should not contain completed todos, fake ids should be filtered too
user.tasksOrder.todos.push('00000000-0000-0000-0000-000000000000');
expect(tasksByType.todos).to.be.lengthOf(2);
expect(user.tasksOrder.todos).to.be.lengthOf(3);
cron({user, tasksByType, daysMissed, analytics});
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
expect(tasksByType.todos).to.be.lengthOf(2);
expect(user.tasksOrder.todos).to.be.lengthOf(1);
});
it('should preserve todos order in task list', () => {
tasksByType.todos = [];
user.tasksOrder.todos = [];
let todo = {
text: 'test todo',
type: 'todo',
value: 0,
};
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
// Set up user.tasksOrder list in a specific order
user.tasksOrder.todos = tasksByType.todos.map(todoTask => {
return todoTask._id;
}).reverse();
let original = user.tasksOrder.todos; // Preserve the original order
cron({user, tasksByType, daysMissed, analytics});
let listsAreEqual = true;
user.tasksOrder.todos.forEach((taskId, index) => {
if (original[index]._id !== taskId) {
listsAreEqual = false;
}
});
expect(listsAreEqual);
expect(user.tasksOrder.todos).to.be.lengthOf(original.length);
});
});
describe('dailys', () => {
@@ -475,7 +475,7 @@ describe('Purchasing a group plan for group', () => {
let updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 4);
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 5);
});
it('adds months to members with existing recurring subscription (Paypal)', async () => {
+54 -1
View File
@@ -13,6 +13,9 @@ import analyticsService from '../../../../../website/server/libs/analyticsServic
import * as cronLib from '../../../../../website/server/libs/cron';
import { v4 as generateUUID } from 'uuid';
const CRON_TIMEOUT_WAIT = new Date(60 * 60 * 1000).getTime();
const CRON_TIMEOUT_UNIT = new Date(60 * 1000).getTime();
describe('cron middleware', () => {
let res, req;
let user;
@@ -235,7 +238,13 @@ describe('cron middleware', () => {
sandbox.spy(cronLib, 'recoverCron');
sandbox.stub(User, 'update')
.withArgs({ _id: user._id, _cronSignature: 'NOT_RUNNING' })
.withArgs({
_id: user._id,
$or: [
{_cronSignature: 'NOT_RUNNING'},
{_cronSignature: {$lt: sinon.match.number}},
],
})
.returns({
exec () {
return Promise.resolve(updatedUser);
@@ -251,4 +260,48 @@ describe('cron middleware', () => {
});
});
});
it('cronSignature less than an hour ago should error', async () => {
user.lastCron = moment(new Date()).subtract({days: 2});
let now = new Date();
await User.update({
_id: user._id,
}, {
$set: {
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT + CRON_TIMEOUT_UNIT,
},
}).exec();
await user.save();
let expectedErrMessage = `Impossible to recover from cron for user ${user._id}.`;
await new Promise((resolve, reject) => {
cronMiddleware(req, res, (err) => {
if (!err) return reject(new Error('Cron should have failed.'));
expect(err.message).to.be.equal(expectedErrMessage);
resolve();
});
});
});
it('cronSignature longer than an hour ago should allow cron', async () => {
user.lastCron = moment(new Date()).subtract({days: 2});
let now = new Date();
await User.update({
_id: user._id,
}, {
$set: {
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT - CRON_TIMEOUT_UNIT,
},
}).exec();
await user.save();
await new Promise((resolve, reject) => {
cronMiddleware(req, res, (err) => {
if (err) return reject(err);
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
expect(user._cronSignature).to.be.equal('NOT_RUNNING');
resolve();
});
});
});
});
+6
View File
@@ -4,6 +4,7 @@ import {
generateNext,
} from '../../../../helpers/api-unit.helper';
import responseMiddleware from '../../../../../website/server/middlewares/response';
import packageInfo from '../../../../../package.json';
describe('response middleware', () => {
let res, req, next;
@@ -34,6 +35,7 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -51,6 +53,7 @@ describe('response middleware', () => {
message: 'hello',
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -67,6 +70,7 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -81,6 +85,7 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: 0,
appVersion: packageInfo.version,
});
});
@@ -104,6 +109,7 @@ describe('response middleware', () => {
},
],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
});
+77 -1
View File
@@ -282,7 +282,7 @@ describe('Group Model', () => {
expect(finishQuest).to.be.calledWith(quest);
});
context('with Rage', () => {
context('with healing Rage', () => {
beforeEach(async () => {
party.quest.active = false;
party.quest.key = 'trex_undead';
@@ -327,6 +327,46 @@ describe('Group Model', () => {
expect(party.quest.progress.hp).to.eql(500);
});
});
context('with Mana drain Rage', () => {
beforeEach(async () => {
party.quest.active = false;
party.quest.key = 'lostMasterclasser4';
await party.startQuest(questLeader);
await party.save();
});
it('applies down progress to boss rage', async () => {
progress.down = -2;
await Group.processQuestProgress(participatingMember, progress);
party = await Group.findOne({_id: party._id});
expect(party.quest.progress.rage).to.eql(8);
let drainedUser = await User.findById(participatingMember._id);
expect(drainedUser.stats.mp).to.eql(10);
});
it('activates rage when progress.down triggers rage bar', async () => {
let quest = questScrolls[party.quest.key];
progress.down = -999;
await party.save();
await Group.processQuestProgress(participatingMember, progress);
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(party.quest.progress.rage).to.eql(0);
let drainedUser = await User.findById(participatingMember._id);
expect(drainedUser.stats.mp).to.eql(0);
});
});
});
context('Collection Quests', () => {
@@ -1342,6 +1382,42 @@ describe('Group Model', () => {
expect(updatedParticipatingMember.achievements.quests[quest.key]).to.eql(1);
});
it('gives out super awesome Masterclasser achievement to the deserving', async () => {
quest = questScrolls.lostMasterclasser4;
party.quest.key = quest.key;
questLeader.achievements.quests = {
mayhemMistiflying1: 1,
mayhemMistiflying2: 1,
mayhemMistiflying3: 1,
stoikalmCalamity1: 1,
stoikalmCalamity2: 1,
stoikalmCalamity3: 1,
taskwoodsTerror1: 1,
taskwoodsTerror2: 1,
taskwoodsTerror3: 1,
dilatoryDistress1: 1,
dilatoryDistress2: 1,
dilatoryDistress3: 1,
lostMasterclasser1: 1,
lostMasterclasser2: 1,
lostMasterclasser3: 1,
};
await questLeader.save();
await party.finishQuest(quest);
let [
updatedLeader,
updatedParticipatingMember,
] = await Promise.all([
User.findById(questLeader._id),
User.findById(participatingMember._id),
]);
expect(updatedLeader.achievements.lostMasterclasser).to.eql(true);
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
});
it('gives xp and gold', async () => {
await party.finishQuest(quest);
-7
View File
@@ -1,7 +0,0 @@
{
"globals": {
"browser": true,
"by": true,
"element": true
}
}
-22
View File
@@ -1,22 +0,0 @@
import fs from 'fs';
import { resetHabiticaDB } from '../../helpers/mongo';
before(async () => {
await resetHabiticaDB();
});
// based on https://github.com/angular/protractor/issues/114#issuecomment-29046939
afterEach(async function () {
let lastTest = this.currentTest;
if (lastTest.state === 'failed') {
let filename = `exception_${lastTest.title}.png`;
let png = await browser.takeScreenshot();
let buffer = new Buffer(png, 'base64');
let stream = fs.createWriteStream(filename);
stream.write(buffer);
stream.end();
}
});
-30
View File
@@ -1,30 +0,0 @@
'use strict';
let chai = require('chai');
let chaiAsPromised = require('chai-as-promised');
require('babel-register');
require('babel-polyfill');
exports.config = {
specs: ['./helper.js', './**/*.test.js'],
baseUrl: 'http://localhost:3003/',
capabilities: {
browserName: 'firefox',
},
directConnect: true,
seleniumAddress: 'http://localhost:4444/wd/hub',
framework: 'mocha',
mochaOpts: {
reporter: 'spec',
slow: 6000,
timeout: 10000,
compilers: 'js:babel-register',
},
onPrepare: () => {
browser.ignoreSynchronization = true;
chai.use(chaiAsPromised);
global.expect = chai.expect;
},
};
@@ -1,64 +0,0 @@
import { v4 as generateUniqueId } from 'uuid';
describe('Static Front Page', () => {
beforeEach(() => {
browser.get('/');
browser.sleep(1000);
});
it('shows the front page', async () => {
let button = element(by.id('play-btn'));
await expect(button.getText()).to.eventually.eql('Join for free');
});
it('does not login when using wrong credentials', async () => {
let button = element(by.id('play-btn'));
let randomName = generateUniqueId();
button.click();
browser.sleep(1000);
element(by.model('loginUsername')).sendKeys(randomName);
element(by.model('loginPassword')).sendKeys('pass');
let login = element(by.css('#loginForm input[value="Login"]'));
login.click();
browser.sleep(1000);
let alertDialog = browser.switchTo().alert();
await expect(alertDialog.getText()).to.eventually.match(/username or password is incorrect./);
alertDialog.accept();
});
it('registers a new user', async function () {
this.timeout(30000); // TODO: Speed up registration action. Takes way too long and times out unless you extend the timeout
let button = element(by.id('play-btn'));
let randomName = generateUniqueId();
button.click();
browser.sleep(1000);
let registerTab = element(by.linkText('Register'));
registerTab.click();
element(by.model('registerVals.username')).sendKeys(randomName);
element(by.model('registerVals.email')).sendKeys(`${randomName}@example.com`);
element(by.model('registerVals.password')).sendKeys('pass');
element(by.model('registerVals.confirmPassword')).sendKeys('pass');
let register = element(by.css('#registrationForm input[type="submit"]'));
register.click();
browser.sleep(3000);
let url = await browser.getCurrentUrl();
expect(url).to.not.match(/static\/front/);
});
it('logs in an existing user');
});
-31
View File
@@ -1,31 +0,0 @@
'use strict';
describe('AppJS', function() {
describe('Automatic page refresh', function(){
var clock;
beforeEach(function () {
clock = sandbox.useFakeTimers();
sandbox.stub(window, "refresher", function(){return true});
});
it('should not call refresher if idle time is less than 6 hours', function() {
window.awaitIdle();
clock.tick(21599999);
expect(window.refresher).to.not.be.called;
});
it('should not call refresher if awaitIdle is called within 6 hours', function() {
window.awaitIdle();
clock.tick(21500000);
window.awaitIdle();
clock.tick(21500000);
expect(window.refresher).to.not.be.called;
});
it('should call refresher if idle time is 6 hours or greater', function() {
window.awaitIdle();
clock.tick(21900000);
expect(window.refresher).to.be.called;
});
});
});
@@ -1,125 +0,0 @@
'use strict';
describe('Auth Controller', function() {
var scope, ctrl, user, $httpBackend, $window, $modal, alert, Auth;
beforeEach(function(){
module(function($provide) {
Auth = {
runAuth: sandbox.spy(),
};
$provide.value('Analytics', analyticsMock);
$provide.value('Chat', { seenMessage: function() {} });
$provide.value('Auth', Auth);
});
inject(function(_$httpBackend_, $rootScope, $controller, _$modal_) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
scope.loginUsername = 'user';
scope.loginPassword = 'pass';
$window = { location: { href: ""}, alert: sandbox.spy() };
$modal = _$modal_;
user = { user: {}, authenticate: sandbox.spy() };
alert = { authErrorAlert: sandbox.spy() };
ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user, Alert: alert});
})
});
describe('logging in', function() {
it('should log in users with correct uname / pass', function() {
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond({data: {id: 'abc', apiToken: 'abc'}});
scope.auth();
$httpBackend.flush();
expect(Auth.runAuth).to.be.calledOnce;
expect(alert.authErrorAlert).to.not.be.called;
});
it('should not log in users with incorrect uname / pass', function() {
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond(404, '');
scope.auth();
$httpBackend.flush();
expect(Auth.runAuth).to.not.be.called;
expect(alert.authErrorAlert).to.be.calledOnce;
});
});
describe('#clearLocalStorage', function () {
var timer;
beforeEach(function () {
timer = sandbox.useFakeTimers();
sandbox.stub($modal, 'open');
});
it('opens modal with message about clearing local storage and logging out', function () {
scope.clearLocalStorage();
expect($modal.open).to.be.calledOnce;
expect($modal.open).to.be.calledWith({
templateUrl: 'modals/message-modal.html',
scope: scope
});
expect(scope.messageModal.title).to.eql(window.env.t('localStorageClearing'));
expect(scope.messageModal.body).to.eql(window.env.t('localStorageClearingExplanation'));
});
it('does not call $scope.logout before 3 seconds', function () {
sandbox.stub(scope, 'logout');
scope.clearLocalStorage();
timer.tick(2999);
expect(scope.logout).to.not.be.called;
});
it('calls $scope.logout after 3 seconds', function () {
sandbox.stub(scope, 'logout');
scope.clearLocalStorage();
timer.tick(3000);
expect(scope.logout).to.be.calledOnce;
});
it('does not clear local storage before 3 seconds', function () {
sandbox.stub(localStorage, 'clear');
scope.clearLocalStorage();
timer.tick(2999);
expect(localStorage.clear).to.not.be.called;
});
it('clears local storage after 3 seconds', function () {
sandbox.stub(localStorage, 'clear');
scope.clearLocalStorage();
timer.tick(3000);
expect(localStorage.clear).to.be.calledOnce;
});
it('does not redirect to /logout route before 3 seconds', function () {
scope.clearLocalStorage();
timer.tick(2999);
expect($window.location.href).to.eql('');
});
it('redirects to /logout after 3 seconds', function () {
scope.clearLocalStorage();
timer.tick(3000);
expect($window.location.href).to.eql('/logout');
});
});
});
@@ -1,120 +0,0 @@
'use strict';
describe("Autocomplete controller", function() {
var scope, ctrl, user, $rootScope, $controller;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function($rootScope, _$controller_){
user = specHelper.newUser();
user._id = "unique-user-id";
scope = $rootScope.$new();
scope.group = {}
scope.group.chat = [];
$controller = _$controller_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('AutocompleteCtrl', {$scope: scope});
});
});
describe("clearUserList", function() {
it('calling the function clears the list of usernames and responses', function() {
scope.response.push("blah");
scope.usernames.push("blub");
scope.clearUserlist();
expect(scope.response).to.be.empty;
expect(scope.usernames).to.be.empty;
});
it('the function is called upon initialization of the controller', function() {
scope.response.push("blah");
scope.response.push("blub");
ctrl = $controller('AutocompleteCtrl', {$scope: scope});
expect(scope.response).to.be.empty;
expect(scope.usernames).to.be.empty;
});
})
describe("filterUser", function() {
it('filters with undefined query (not loaded yet) and returns false (so it will not be rendered)', function() {
expect(scope.filterUser({user: "boo"})).to.be.eq(false);
});
it('filters with null query (no typing yet) and returns false (so it will not be rendered)', function() {
scope.query = null
expect(scope.filterUser({user: "boo"})).to.be.eq(false);
});
it('filters with empty prefix and returns true', function() {
scope.query = {text: ""};
expect(scope.filterUser({user: "prefix"})).to.be.eq(true);
});
it('filters with prefix element and returns true', function() {
scope.query = {text: "pre"}
expect(scope.filterUser({user: "prefix"})).to.be.eq(true);
});
it('filters with prefix element of a different case and returns true', function() {
scope.query = {text: "pre"}
expect(scope.filterUser({user: "Prefix"})).to.be.eq(true);
});
it('filters with nonprefix element and returns false', function() {
scope.query = {text: "noprefix"}
expect(scope.filterUser({user: "prefix"})).to.be.eq(false);
});
it('filters out system messages (messages without username)', function() {
scope.query = {text: "myquery"}
expect(scope.filterUser({uuid: "system"})).to.be.eq(false);
});
});
describe("performCompletion", function() {
it('triggers autoComplete', function() {
scope.autoComplete = sandbox.spy();
var msg = {user: "boo"}; // scope.autoComplete only cares about user
scope.query = {text: "b"};
scope.performCompletion(msg);
expect(scope.query).to.be.eq(null);
expect(scope.autoComplete.callCount).to.be.eq(1);
expect(scope.autoComplete).to.have.been.calledWith(msg);
});
});
describe("addNewUser", function() {
it('a new message from a new user will modify the usernames', function() {
expect(scope.response).to.be.empty;
expect(scope.usernames).to.be.empty;
var msg = {user: "boo"};
scope.addNewUser(msg);
expect(scope.response[0]).to.be.eq(msg);
expect(scope.usernames[0]).to.be.eq("boo");
});
});
describe("chatChanged", function() {
it('if a new chat arrives, the new user name is extracted', function() {
var chatChanged = sandbox.spy(scope, 'chatChanged');
scope.$watch('group.chat',scope.chatChanged); // reinstantiate watch so spy works
scope.$digest(); // trigger watch
scope.group.chat.push({msg: "new chat", user: "boo"});
expect(chatChanged.callCount).to.be.eq(1);
});
});
});
@@ -1,774 +0,0 @@
'use strict';
describe('Challenges Controller', function() {
var rootScope, scope, user, User, ctrl, groups, members, notification, state, challenges, tasks, tavernId;
beforeEach(function() {
module(function($provide) {
user = specHelper.newUser();
User = {
getBalanceInGems: sandbox.stub(),
sync: sandbox.stub(),
user: user
}
$provide.value('User', User);
});
inject(function($rootScope, $controller, _$state_, _Groups_, _Members_, _Notification_, _Challenges_, _Tasks_, _TAVERN_ID_){
scope = $rootScope.$new();
rootScope = $rootScope;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: User});
ctrl = $controller('ChallengesCtrl', {$scope: scope, User: User});
challenges = _Challenges_;
tasks = _Tasks_;
groups = _Groups_;
members = _Members_;
notification = _Notification_;
state = _$state_;
tavernId = _TAVERN_ID_;
});
});
context('filtering', function() {
describe('filterChallenges', function() {
var ownMem, ownNotMem, notOwnMem, notOwnNotMem;
beforeEach(function() {
ownMem = specHelper.newChallenge({
description: 'You are the owner and member',
leader: user._id,
members: [user],
_isMember: true,
_id: 'ownMem-id',
});
ownNotMem = specHelper.newChallenge({
description: 'You are the owner, but not a member',
leader: user._id,
members: [],
_isMember: false,
_id: 'ownNotMem-id',
});
notOwnMem = specHelper.newChallenge({
description: 'Not owner but a member',
leader: {_id:"test"},
members: [user],
_isMember: true,
_id: 'notOwnMem-id',
});
notOwnNotMem = specHelper.newChallenge({
description: 'Not owner or member',
leader: {_id:"test"},
members: [],
_isMember: false,
_id: 'notOwnNotMem-id',
});
user.challenges = [ownMem._id, notOwnMem._id];
scope.search = {
group: _.transform(groups, function(m,g){m[g._id]=true;})
};
});
it('displays challenges that match membership: either and owner: either', function() {
scope.search._isMember = 'either';
scope.search._isOwner = 'either';
expect(scope.filterChallenges(ownMem)).to.eql(true);
expect(scope.filterChallenges(ownNotMem)).to.eql(true);
expect(scope.filterChallenges(notOwnMem)).to.eql(true);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(true);
});
it('displays challenges that match membership: either and owner: true', function() {
scope.search._isMember = 'either';
scope.search._isOwner = true;
expect(scope.filterChallenges(ownMem)).to.eql(true);
expect(scope.filterChallenges(ownNotMem)).to.eql(true);
expect(scope.filterChallenges(notOwnMem)).to.eql(false);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(false);
});
it('displays challenges that match membership: either and owner: false', function() {
scope.search._isMember = 'either';
scope.search._isOwner = false;
expect(scope.filterChallenges(ownMem)).to.eql(false);
expect(scope.filterChallenges(ownNotMem)).to.eql(false);
expect(scope.filterChallenges(notOwnMem)).to.eql(true);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(true);
});
it('displays challenges that match membership: true and owner: either', function() {
scope.search._isMember = true;
scope.search._isOwner = 'either';
expect(scope.filterChallenges(ownMem)).to.eql(true);
expect(scope.filterChallenges(ownNotMem)).to.eql(false);
expect(scope.filterChallenges(notOwnMem)).to.eql(true);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(false);
});
it('displays challenges that match membership: true and owner: true', function() {
scope.search._isMember = true;
scope.search._isOwner = true;
expect(scope.filterChallenges(ownMem)).to.eql(true);
expect(scope.filterChallenges(ownNotMem)).to.eql(false);
expect(scope.filterChallenges(notOwnMem)).to.eql(false);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(false);
});
it('displays challenges that match membership: true and owner: false', function() {
scope.search._isMember = true;
scope.search._isOwner = false;
expect(scope.filterChallenges(ownMem)).to.eql(false);
expect(scope.filterChallenges(ownNotMem)).to.eql(false);
expect(scope.filterChallenges(notOwnMem)).to.eql(true);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(false);
});
it('displays challenges that match membership: false and owner: either', function() {
scope.search._isMember = false;
scope.search._isOwner = 'either';
expect(scope.filterChallenges(ownMem)).to.eql(false);
expect(scope.filterChallenges(ownNotMem)).to.eql(true);
expect(scope.filterChallenges(notOwnMem)).to.eql(false);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(true);
});
it('displays challenges that match membership: false and owner: true', function() {
scope.search._isMember = false;
scope.search._isOwner = true;
expect(scope.filterChallenges(ownMem)).to.eql(false);
expect(scope.filterChallenges(ownNotMem)).to.eql(true);
expect(scope.filterChallenges(notOwnMem)).to.eql(false);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(false);
});
it('displays challenges that match membership: false and owner: false', function() {
scope.search._isMember = false;
scope.search._isOwner = false;
expect(scope.filterChallenges(ownMem)).to.eql(false);
expect(scope.filterChallenges(ownNotMem)).to.eql(false);
expect(scope.filterChallenges(notOwnMem)).to.eql(false);
expect(scope.filterChallenges(notOwnNotMem)).to.eql(true);
});
it('filters challenges to a single group when group id filter is set', inject(function($controller) {
scope.search = { };
scope.groupsFilter = {
0: specHelper.newGroup({_id: 'group-one'}),
1: specHelper.newGroup({_id: 'group-two'}),
2: specHelper.newGroup({_id: 'group-three'})
};
scope.groupIdFilter = 'group-one';
scope.filterInitialChallenges();
expect(scope.search.group).to.eql({'group-one': true});
}));
});
describe('selectAll', function() {
it('sets all groups in seach.group to true', function() {
scope.search = { };
scope.groupsFilter = {
0: specHelper.newGroup({_id: 'group-one'}),
1: specHelper.newGroup({_id: 'group-two'}),
2: specHelper.newGroup({_id: 'group-three'})
};
scope.selectAll();
expect(scope.search.group).to.eql({
'group-one': true,
'group-two': true,
'group-three': true
});
});
});
describe('selectNone', function() {
it('sets all groups in seach.group to false', function() {
scope.search = { };
scope.groupsFilter = {
0: specHelper.newGroup({_id: 'group-one'}),
1: specHelper.newGroup({_id: 'group-two'}),
2: specHelper.newGroup({_id: 'group-three'})
};
scope.selectNone();
expect(scope.search.group).to.eql({
'group-one': false,
'group-two': false,
'group-three': false
});
});
});
});
context('task manipulation', function() {
describe('shouldShow', function() {
it('overrides task controller function by always returning true', function() {
expect(scope.shouldShow()).to.eq(true);
});
});
describe('addTask', function() {
var challenge;
beforeEach(function () {
challenge = specHelper.newChallenge({
description: 'You are the owner and member',
leader: user._id,
members: [user],
_isMember: true
});
});
it('adds default task to array', function() {
var taskArray = [];
var listDef = {
newTask: 'new todo text',
type: 'todo'
}
scope.addTask(listDef, challenge);
expect(challenge['todos'].length).to.eql(1);
expect(challenge['todos'][0].text).to.eql('new todo text');
expect(challenge['todos'][0].type).to.eql('todo');
});
it('adds the task to the front of the array', function() {
var previousTask = specHelper.newTodo({ text: 'previous task' });
var taskArray = [];
challenge['todos'] = [previousTask];
var listDef = {
newTask: 'new todo',
type: 'todo'
}
scope.addTask(listDef, challenge);
expect(challenge['todos'].length).to.eql(2);
expect(challenge['todos'][0].text).to.eql('new todo');
expect(challenge['todos'][1].text).to.eql('previous task');
});
it('removes text from new task input box', function() {
var taskArray = [];
var listDef = {
newTask: 'new todo text',
type: 'todo'
}
scope.addTask(listDef, challenge);
expect(listDef.newTask).to.not.exist;
});
});
describe('editTask', function() {
it('is Tasks.editTask', function() {
inject(function(Tasks) {
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
// expect(scope.editTask).to.eql(Tasks.editTask);
});
});
});
describe('removeTask', function() {
var task, challenge;
beforeEach(function() {
sandbox.stub(window, 'confirm');
task = specHelper.newTodo();
challenge = specHelper.newChallenge({
description: 'You are the owner and member',
leader: user._id,
members: [user],
_isMember: true
});
challenge['todos'] = [task];
});
it('asks user to confirm deletion', function() {
scope.removeTask(task, challenge);
expect(window.confirm).to.be.calledOnce;
});
it('does not remove task from list if not confirmed', function() {
window.confirm.returns(false);
scope.removeTask(task, challenge);
expect(challenge['todos']).to.include(task);
});
it('removes task from list', function() {
window.confirm.returns(true);
scope.removeTask(task, challenge);
expect(challenge['todos']).to.not.include(task);
});
});
describe('saveTask', function() {
it('sets task._editing to false', function() {
var task = specHelper.newTask({ _editing: true });
scope.saveTask(task);
expect(task._editing).to.be.eql(false);
});
});
});
context('challenge owner interactions', function() {
describe("save challenge", function() {
var alert, createChallengeSpy, challengeResponse, taskChallengeCreateSpy;
beforeEach(function(){
alert = sandbox.stub(window, "alert");
createChallengeSpy = sinon.stub(challenges, 'createChallenge');
challengeResponse = {data: {data: {_id: 'new-challenge'}}};
createChallengeSpy.returns(Promise.resolve(challengeResponse));
taskChallengeCreateSpy = sinon.stub(tasks, 'createChallengeTasks');
var taskResponse = {data: {data: []}};
taskChallengeCreateSpy.returns(Promise.resolve(taskResponse));
});
it("opens an alert box if challenge.group is not specified", function() {
var challenge = specHelper.newChallenge({
name: 'Challenge without a group',
shortName: 'chal without group',
group: null
});
scope.save(challenge);
expect(alert).to.be.calledOnce;
expect(alert).to.be.calledWith(window.env.t('selectGroup'));
});
it("opens an alert box if isNew and user does not have enough gems", function() {
var challenge = specHelper.newChallenge({
name: 'Challenge without enough gems',
shortName: 'chal without gem',
prize: 5
});
scope.maxPrize = 4;
scope.save(challenge);
expect(alert).to.be.calledOnce;
expect(alert).to.be.calledWith(window.env.t('challengeNotEnoughGems'));
});
it("saves the challenge if user does not have enough gems, but the challenge is not new", function() {
var updateChallengeSpy = sinon.spy(challenges, 'updateChallenge');
var challenge = specHelper.newChallenge({
_id: 'challenge-has-id-so-its-not-new',
name: 'Challenge without enough gems',
shortName: 'chal without gem',
prize: 5,
});
scope.maxPrize = 0;
scope.save(challenge);
expect(updateChallengeSpy).to.be.calledOnce;
expect(alert).to.not.be.called;
});
it("saves the challenge if user has enough gems and challenge is new", function() {
var challenge = specHelper.newChallenge({
name: 'Challenge without enough gems',
shortName: 'chal without gem',
prize: 5,
});
scope.maxPrize = 5;
scope.save(challenge);
expect(createChallengeSpy).to.be.calledOnce;
expect(alert).to.not.be.called;
});
it('saves challenge and then proceeds to detail page', function(done) {
sandbox.stub(state, 'transitionTo');
var challenge = specHelper.newChallenge({
name: 'Challenge',
shortName: 'chal',
});
setTimeout(function() {
expect(createChallengeSpy).to.be.calledOnce;
expect(state.transitionTo).to.be.calledWith(
'options.social.challenges.detail',
{ cid: 'new-challenge' },
{
reload: true, inherit: false, notify: true
}
);
done();
}, 1000);
scope.save(challenge);
});
it('saves new challenge and syncs User', function(done) {
var challenge = specHelper.newChallenge();
challenge.shortName = 'chal';
setTimeout(function() {
expect(User.sync).to.be.calledOnce;
done();
}, 1000);
scope.save(challenge);
});
it('saves new challenge and syncs User', function(done) {
sinon.stub(notification, 'text');
var challenge = specHelper.newChallenge();
challenge.shortName = 'chal';
setTimeout(function() {
expect(notification.text).to.be.calledOnce;
expect(notification.text).to.be.calledWith(window.env.t('challengeCreated'));
done();
}, 1000);
scope.save(challenge);
});
});
describe('create', function() {
it('creates new challenge with group that user has selected in filter', function() {
var party = specHelper.newGroup({
type: 'party',
_id: 'user-party'
});
scope.groupsFilter = [party];
scope.search = {
group: {
'user-party': true
}
};
scope.create();
expect(scope.newChallenge.group).to.eql('user-party');
});
it('uses first group in $scope.groups if more than one exists', function() {
var party = specHelper.newGroup({
type: 'party',
_id: 'user-party'
});
var guild = specHelper.newGroup({
type: 'guild',
_id: 'guild'
});
scope.groups = [party, guild];
scope.groupsFilter = [party, guild];
scope.search = {
group: {
'user-party': true,
'guild': true
}
};
scope.create();
expect(scope.newChallenge.group).to.eql('user-party');
});
it('defaults to tavern if no group can be set as default', function() {
scope.create();
expect(scope.newChallenge.group).to.eql(tavernId);
});
it('calculates maxPrize', function() {
User.getBalanceInGems.returns(20);
scope.create();
expect(scope.maxPrize).to.eql(20);
});
it('sets newChallenge to a blank challenge', function() {
scope.create();
var chal = scope.newChallenge;
expect(chal.name).to.eql('');
expect(chal.description).to.eql('');
expect(chal.habits).to.eql([]);
expect(chal.dailys).to.eql([]);
expect(chal.todos).to.eql([]);
expect(chal.rewards).to.eql([]);
expect(chal.leader).to.eql('unique-user-id');
expect(chal.group).to.eql(tavernId);
expect(chal.timestamp).to.be.greaterThan(0);
expect(chal.official).to.eql(false);
});
});
describe('insufficientGemsForTavernChallenge', function() {
context('tavern challenge', function() {
it('returns true if user has no gems', function() {
User.user.balance = 0;
scope.newChallenge = specHelper.newChallenge({
group: tavernId
});
var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge();
expect(cannotCreateTavernChallenge).to.eql(true);
});
it('returns false if user has gems', function() {
User.user.balance = .25;
scope.newChallenge = specHelper.newChallenge({
group: tavernId
});
var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge();
expect(cannotCreateTavernChallenge).to.eql(false);
});
});
context('non-tavern challenge', function() {
it('returns false', function() {
User.user.balance = 0;
scope.newChallenge = specHelper.newChallenge({
group: 'not-tavern'
});
var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge();
expect(cannotCreateTavernChallenge).to.eql(false);
});
});
});
describe('edit', function() {
it('transitions to edit page', function() {
sandbox.stub(state, 'transitionTo');
var challenge = specHelper.newChallenge({
_id: 'challenge-id'
});
scope.edit(challenge);
expect(state.transitionTo).to.be.calledOnce;
expect(state.transitionTo).to.be.calledWith(
'options.social.challenges.edit',
{ cid: challenge._id },
{ reload: true, inherit: false, notify: true }
);
});
});
describe('discard', function() {
it('sets new challenge to null', function() {
scope.newChallenge = specHelper.newChallenge();
scope.discard();
expect(scope.newChallenge).to.not.exist;
});
});
describe('clone', function() {
var challengeToClone = {
name: 'copyChallenge',
description: 'copyChallenge',
habits: [specHelper.newHabit()],
dailys: [specHelper.newDaily()],
todos: [specHelper.newTodo()],
rewards: [specHelper.newReward()],
leader: 'unique-user-id',
group: { _id: "copyGroup" },
timestamp: new Date("October 13, 2014 11:13:00"),
members: ['id', 'another-id'],
official: true,
_isMember: true,
prize: 1
};
it('Clones the basic challenge info', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.name).to.eql(challengeToClone.name);
expect(scope.newChallenge.shortName).to.eql(challengeToClone.shortName);
expect(scope.newChallenge.description).to.eql(challengeToClone.description);
expect(scope.newChallenge.leader).to.eql(user._id);
expect(scope.newChallenge.group).to.eql(challengeToClone.group._id);
expect(scope.newChallenge.official).to.eql(challengeToClone.official);
expect(scope.newChallenge.prize).to.eql(challengeToClone.prize);
});
it('does not clone members', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.members).to.not.exist;
});
it('does not clone timestamp', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.timestamp).to.not.exist;
});
it('clones habits', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.habits.length).to.eql(challengeToClone.habits.length);
expect(scope.newChallenge.habits[0].text).to.eql(challengeToClone.habits[0].text);
expect(scope.newChallenge.habits[0].notes).to.eql(challengeToClone.habits[0].notes);
});
it('clones dailys', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.dailys.length).to.eql(challengeToClone.dailys.length);
expect(scope.newChallenge.dailys[0].text).to.eql(challengeToClone.dailys[0].text);
expect(scope.newChallenge.dailys[0].notes).to.eql(challengeToClone.dailys[0].notes);
});
it('clones todos', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.todos.length).to.eql(challengeToClone.todos.length);
expect(scope.newChallenge.todos[0].text).to.eql(challengeToClone.todos[0].text);
expect(scope.newChallenge.todos[0].notes).to.eql(challengeToClone.todos[0].notes);
});
it('clones rewards', function() {
scope.clone(challengeToClone);
expect(scope.newChallenge.rewards.length).to.eql(challengeToClone.rewards.length);
expect(scope.newChallenge.rewards[0].text).to.eql(challengeToClone.rewards[0].text);
expect(scope.newChallenge.rewards[0].notes).to.eql(challengeToClone.rewards[0].notes);
});
});
});
context('User interactions', function() {
describe('join', function() {
it('calls challenge join', function(){
var joinChallengeSpy = sinon.spy(challenges, 'joinChallenge');
var challenge = specHelper.newChallenge({
_id: 'challenge-to-join',
});
scope.join(challenge);
expect(joinChallengeSpy).to.be.calledOnce;
});
});
describe('clickLeave', function() {
var clickEvent = {
target: 'button'
};
it('sets selectedChal to passed in challenge', function() {
var challenge = specHelper.newChallenge({
_id: 'popover-challenge-to-leave'
});
expect(scope.selectedChal).to.not.exist;
scope.clickLeave(challenge, clickEvent);
expect(scope.selectedChal).to.eql(challenge);
});
it('creates popover element', function() {
var challenge = specHelper.newChallenge({
_id: 'popover-challenge-to-leave'
});
expect(scope.popoverEl).to.not.exist;
scope.clickLeave(challenge, clickEvent);
expect(scope.popoverEl).to.exist;
});
});
describe('leave', function() {
var challenge = specHelper.newChallenge({
_id: 'challenge-to-leave',
});
var clickEvent = {
target: 'button'
};
it('removes selectedChal when cancel is chosen', function() {
scope.clickLeave(challenge, clickEvent);
expect(scope.selectedChal).to.eql(challenge);
scope.leave('cancel');
expect(scope.selectedChal).to.not.exist;
});
it('calls challenge leave when anything but cancel is chosen', function() {
var leaveChallengeSpy = sinon.spy(challenges, 'leaveChallenge');
scope.clickLeave(challenge, clickEvent);
scope.leave('not-cancel', challenge);
expect(leaveChallengeSpy).to.be.calledOnce;
});
});
});
context('modal actions', function() {
beforeEach(function() {
sandbox.stub(members, 'selectMember');
sandbox.stub(rootScope, 'openModal');
members.selectMember.returns(Promise.resolve());
});
describe('sendMessageToChallengeParticipant', function() {
it('opens private-message modal', function(done) {
scope.sendMessageToChallengeParticipant(user._id);
setTimeout(function() {
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'private-message',
{ controller: 'MemberModalCtrl' }
);
done();
}, 1000);
});
});
describe('sendGiftToChallengeParticipant', function() {
it('opens send-gift modal', function(done) {
scope.sendGiftToChallengeParticipant(user._id);
setTimeout(function() {
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'send-gift',
{ controller: 'MemberModalCtrl' }
);
done();
}, 1000);
});
});
});
});
@@ -1,121 +0,0 @@
'use strict';
describe("Chat Controller", function() {
var scope, ctrl, user, $rootScope, $controller, $httpBackend, html;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function(_$rootScope_, _$controller_, _$compile_, _$httpBackend_){
user = specHelper.newUser();
user._id = "unique-user-id";
$rootScope = _$rootScope_;
scope = _$rootScope_.$new();
$controller = _$controller_;
$httpBackend = _$httpBackend_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
html = _$compile_('<div><form ng-submit="postChat(group, message.content)"><textarea submit-on-meta-enter ng-model="message.content" ng-model-options="{debounce: 250}"></textarea></form></div>')(scope);
document.body.appendChild(html[0]);
ctrl = $controller('ChatCtrl', {$scope: scope, $element: html});
});
});
afterEach(function() {
html.remove();
});
describe('copyToDo', function() {
it('when copying a user message it opens modal with information from message', function() {
scope.group = {
name: "Princess Bride"
};
var modalSpy = sandbox.spy($rootScope, "openModal");
var message = {
uuid: 'the-dread-pirate-roberts',
user: 'Wesley',
text: 'As you wish'
};
scope.copyToDo(message);
modalSpy.should.have.been.calledOnce;
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == message.text
}));
});
it('when copying a system message it opens modal with information from message', function() {
scope.group = {
name: "Princess Bride"
};
var modalSpy = sandbox.spy($rootScope, "openModal");
var message = {
uuid: 'system',
text: 'Wesley attacked the ROUS in the Fire Swamp'
};
scope.copyToDo(message);
modalSpy.should.have.been.calledOnce;
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == message.text
}));
});
});
it('updates model on enter key press', function() {
// Set initial state of the page with some dummy data.
scope.group = { name: 'group' };
// The main controller is going to try to fetch the template right off.
// No big deal, just return an empty string.
$httpBackend.when('GET', 'partials/main.html').respond('');
// Let the page settle, and the controllers set their initial state.
$rootScope.$digest();
// Watch for calls to postChat & make sure it doesn't do anything.
let postChatSpy = sandbox.stub(scope, 'postChat');
// Pretend we typed 'aaa' into the textarea.
var textarea = html.find('textarea');
textarea[0].value = 'aaa';
let inputEvent = new Event('input');
textarea[0].dispatchEvent(inputEvent);
// Give a change for the ng-model watchers to notice that the value in the
// textarea has changed.
$rootScope.$digest();
// Since no time has elapsed and we debounce the model change, we should
// see no model update just yet.
expect(scope.message.content).to.equal('');
// Now, press the enter key in the textarea. We use jquery here to paper
// over browser differences with initializing the keyboard event.
var keyboardEvent = jQuery.Event('keydown', {keyCode: 13, key: 'Enter', metaKey: true});
jQuery(textarea).trigger(keyboardEvent);
// Now, allow the model to update given the changes to the page still
// without letting any time elapse...
$rootScope.$digest();
// ... and nevertheless seeing the desired call to postChat with the right
// data. Yay!
postChatSpy.should.have.been.calledWith(scope.group, 'aaa');
});
});
@@ -1,59 +0,0 @@
'use strict';
describe("CopyMessageModal controller", function() {
var scope, ctrl, user, Notification, $rootScope, $controller;
beforeEach(function() {
module(function($provide) {
var mockWindow = {href: '', alert: sandbox.spy(), location: {search: '', pathname: '', href: ''}};
$provide.value('$window', mockWindow);
});
inject(function($rootScope, _$controller_, _Notification_, User){
user = specHelper.newUser();
user._id = "unique-user-id";
user.ops = {
addTask: sandbox.spy()
};
scope = $rootScope.$new();
scope.$close = sandbox.spy();
$controller = _$controller_;
User.setUser(user);
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: User});
ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: User});
Notification = _Notification_;
Notification.text = sandbox.spy();
});
});
describe("saveTodo", function() {
it('saves todo', function() {
scope.text = "A Tavern msg";
scope.notes = "Some notes";
var payload = {
body: {
text: scope.text,
type: 'todo',
notes: scope.notes
}
};
scope.saveTodo();
user.ops.addTask.should.have.been.calledOnce;
user.ops.addTask.should.have.been.calledWith(payload);
Notification.text.should.have.been.calledOnce;
Notification.text.should.have.been.calledWith(window.env.t('messageAddedAsToDo'));
scope.$close.should.have.been.calledOnce;
});
});
});
@@ -1,51 +0,0 @@
'use strict';
describe('Filters Controller', function() {
var scope, user, userService;
beforeEach(function () {
module(function($provide) {
var mockWindow = {href: '', alert: sandbox.spy(), location: {search: '', pathname: '', href: ''}};
$provide.value('$window', mockWindow);
});
inject(function($rootScope, $controller, Shared, User) {
user = specHelper.newUser();
Shared.wrap(user);
scope = $rootScope.$new();
// user.filters = {};
User.setUser(user);
User.user.filters = {};
userService = User;
$controller('FiltersCtrl', {$scope: scope, User: User});
})
});
describe('tags', function(){
it('creates a tag', function(){
scope._newTag = {name:'tagName'}
scope.createTag();
expect(user.tags).to.have.length(1);
expect(user.tags[0].name).to.eql('tagName');
expect(user.tags[0]).to.have.property('id');
});
it('toggles tag filtering', inject(function(Shared){
var tag = {id: Shared.uuid(), name: 'myTag'};
scope.toggleFilter(tag);
expect(userService.user.filters[tag.id]).to.eql(true);
scope.toggleFilter(tag);
expect(userService.user.filters[tag.id]).to.not.eql(true);
}));
});
describe('updateTaskFilter', function(){
it('updatest user\'s filter query with the value of filterQuery', function () {
scope.filterQuery = 'task';
scope.updateTaskFilter();
expect(userService.user.filterQuery).to.eql(scope.filterQuery);
});
});
});
@@ -1,86 +0,0 @@
'use strict';
describe('Footer Controller', function() {
var scope, user, User;
beforeEach(inject(function($rootScope, $controller) {
user = specHelper.newUser();
User = {
log: sandbox.stub(),
set: sandbox.stub(),
addTenGems: sandbox.stub(),
addHourglass: sandbox.stub(),
user: user
};
scope = $rootScope.$new();
$controller('FooterCtrl', {$scope: scope, User: User, Social: {}});
}));
context('Debug mode', function() {
before(function() {
window.env.NODE_ENV = 'test';
});
after(function() {
delete window.env.NODE_ENV;
});
describe('#setHealthLow', function(){
it('sets user health to 1');
});
describe('#addMissedDay', function(){
beforeEach(function() {
sandbox.stub(confirm).returns(true);
});
it('Cancels if confirm box is not confirmed');
it('allows multiple days');
it('sets users last cron');
it('notifies uers');
});
describe('#addTenGems', function() {
it('posts to /user/addTenGems', inject(function($httpBackend) {
scope.addTenGems();
expect(User.addTenGems).to.have.been.called;
}));
});
describe('#addHourglass', function() {
it('posts to /user/addHourglass', inject(function($httpBackend) {
scope.addHourglass();
expect(User.addHourglass).to.have.been.called;
}));
});
describe('#addGold', function() {
it('adds 500 gold to user');
});
describe('#addMana', function() {
it('adds 500 mana to user');
});
describe('#addLevelsAndGold', function() {
it('adds 10000 experience to user');
it('adds 10000 gp to user');
it('adds 10000 mp to user');
});
describe('#addOneLevel', function() {
it('adds one level to user');
});
describe('#addBossQuestProgressUp', function() {
it('adds 1000 progress to quest.progress.up');
});
});
});
@@ -1,279 +0,0 @@
'use strict';
describe('Groups Controller', function() {
var scope, ctrl, groups, user, guild, $rootScope;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function($rootScope, $controller, Groups){
user = specHelper.newUser();
user._id = "unique-user-id";
scope = $rootScope.$new();
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('GroupsCtrl', {$scope: scope, User: {user: user}});
groups = Groups;
});
});
describe("isMemberOfPendingQuest", function() {
var party;
var partyStub;
beforeEach(function () {
party = specHelper.newGroup({
_id: "unique-party-id",
type: 'party',
members: ['leader-id'] // Ensure we wouldn't pass automatically.
});
partyStub = sandbox.stub(groups, "party", function() {
return party;
});
});
it("returns false if group is does not have a quest", function() {
expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok;
});
it("returns false if group quest has not members", function() {
party.quest = {
'key': 'random-key',
};
expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok;
});
it("returns false if group quest is active", function() {
party.quest = {
'key': 'random-key',
'members': {},
'active': true,
};
party.quest.members[user._id] = true;
expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok;
});
it("returns true if user is a member of a pending quest", function() {
party.quest = {
'key': 'random-key',
'members': {},
};
party.quest.members[user._id] = true;
expect(scope.isMemberOfPendingQuest(user._id, party)).to.be.ok;
});
});
describe("isMemberOfGroup", function() {
it("returns true if group is the user's party retrieved from groups service", function() {
var party = specHelper.newGroup({
_id: "unique-party-id",
type: 'party',
members: ['leader-id'] // Ensure we wouldn't pass automatically.
});
var partyStub = sandbox.stub(groups, "party", function() {
return party;
});
expect(scope.isMemberOfGroup(user._id, party)).to.be.ok;
});
it('returns true if guild is included in myGuilds call', function(){
var guild = specHelper.newGroup({
_id: "unique-guild-id",
type: 'guild',
members: [user._id]
});
user.guilds = [guild._id];
expect(scope.isMemberOfGroup(user._id, guild)).to.be.ok;
});
it('does not return true if guild is not included in myGuilds call', function(){
var guild = specHelper.newGroup({
_id: "unique-guild-id",
type: 'guild',
members: ['not-user-id']
});
user.guilds = [];
expect(scope.isMemberOfGroup(user._id, guild)).to.not.be.ok;
});
});
describe('isAbleToEditGroup', () => {
var guild;
beforeEach(() => {
user.contributor = {};
guild = specHelper.newGroup({
_id: 'unique-guild-id',
type: 'guild',
members: ['not-user-id'],
$save: sandbox.spy(),
});
});
it('returns true if user is an admin', () => {
guild.leader = 'not-user-id';
user.contributor.admin = true;
expect(scope.isAbleToEditGroup(guild)).to.be.ok;
});
it('returns true if user is group leader', () => {
guild.leader = {_id: user._id}
expect(scope.isAbleToEditGroup(guild)).to.be.ok;
});
it('returns false is user is not a leader or admin', () => {
expect(scope.isAbleToEditGroup(guild)).to.not.be.ok;
});
it('returns false is user is an admin but group is a party', () => {
guild.type = 'party';
user.contributor.admin = true;
expect(scope.isAbleToEditGroup(guild)).to.not.be.ok;
});
});
describe('editGroup', () => {
var guild;
beforeEach(() => {
guild = specHelper.newGroup({
_id: 'unique-guild-id',
leader: 'old leader',
type: 'guild',
members: ['not-user-id'],
$save: sandbox.spy(),
});
});
it('marks group as being in edit mode', () => {
scope.editGroup(guild);
expect(guild._editing).to.eql(true);
});
it('copies group to groupCopy', () => {
scope.editGroup(guild);
for (var key in scope.groupCopy) {
expect(scope.groupCopy[key]).to.eql(guild[key]);
}
});
it('does not change original group when groupCopy is changed', () => {
scope.editGroup(guild);
scope.groupCopy.leader = 'new leader';
expect(scope.groupCopy.leader).to.not.eql(guild.leader);
});
});
describe('saveEdit', () => {
let guild;
beforeEach(() => {
guild = specHelper.newGroup({
_id: 'unique-guild-id',
name: 'old name',
leader: 'old leader',
type: 'guild',
members: ['not-user-id'],
$save: () => {},
});
scope.editGroup(guild);
});
it('calls group update', () => {
let guildUpdate = sandbox.spy(groups.Group, 'update');
scope.saveEdit(guild);
expect(guildUpdate).to.be.calledOnce;
});
it('calls cancelEdit', () => {
sandbox.stub(scope, 'cancelEdit');
scope.saveEdit(guild);
expect(scope.cancelEdit).to.be.calledOnce;
});
it('applies changes to groupCopy to original group', () => {
scope.groupCopy.name = 'new name';
scope.saveEdit(guild);
expect(guild.name).to.eql('new name');
});
it('assigns leader id to group if leader has changed', () => {
scope.groupCopy._newLeader = { _id: 'some leader id' };
scope.saveEdit(guild);
expect(guild.leader).to.eql('some leader id');
});
it('does not assign new leader id if leader object is not passed in', () => {
scope.groupCopy._newLeader = 'not an object';
scope.saveEdit(guild);
expect(guild.leader).to.eql('old leader');
});
});
describe('cancelEdit', () => {
beforeEach(() => {
guild = specHelper.newGroup({
_id: 'unique-guild-id',
name: 'old name',
leader: 'old leader',
type: 'guild',
members: ['not-user-id'],
$save: () => {},
});
scope.editGroup(guild);
});
it('sets _editing to false on group', () => {
expect(guild._editing).to.eql(true);
scope.cancelEdit(guild);
expect(guild._editing).to.eql(false);
});
it('reset groupCopy to an empty object', () => {
expect(scope.groupCopy).to.not.eql({});
scope.cancelEdit(guild);
expect(scope.groupCopy).to.eql({});
});
});
/* TODO: Modal testing */
describe.skip("deleteAllMessages", function() { });
describe.skip("clickMember", function() { });
describe.skip("removeMember", function() { });
describe.skip("confirmRemoveMember", function() { });
describe.skip("quickReply", function() { });
});
@@ -1,63 +0,0 @@
describe('Group Tasks Meta Actions Controller', () => {
let rootScope, scope, user, userSerivce;
beforeEach(() => {
module(function($provide) {
$provide.value('User', {});
});
inject(($rootScope, $controller) => {
rootScope = $rootScope;
user = specHelper.newUser();
user._id = "unique-user-id";
userSerivce = {user: user};
scope = $rootScope.$new();
scope.task = {
group: {
assignedUsers: [],
approval: {
required: false,
}
},
};
scope.task._edit = angular.copy(scope.task);
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
});
});
describe('toggleTaskRequiresApproval', function () {
it('toggles task approval required field from false to true', function () {
scope.toggleTaskRequiresApproval();
expect(scope.task._edit.group.approval.required).to.be.true;
});
it('toggles task approval required field from true to false', function () {
scope.task._edit.group.approval.required = true;
scope.toggleTaskRequiresApproval();
expect(scope.task._edit.group.approval.required).to.be.false;
});
});
describe('assign events', function () {
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
var testId = 'test-id';
rootScope.$broadcast('addedGroupMember', testId);
expect(scope.task.group.assignedUsers).to.contain(testId);
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
});
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
var testId = 'test-id';
scope.task.group.assignedUsers.push(testId);
scope.task._edit.group.assignedUsers.push(testId);
rootScope.$broadcast('removedGroupMember', testId);
expect(scope.task.group.assignedUsers).to.not.contain(testId);
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
});
});
});
@@ -1,42 +0,0 @@
describe('Group Task Actions Controller', () => {
let scope, user, userSerivce;
beforeEach(() => {
module(function($provide) {
$provide.value('User', {});
});
inject(($rootScope, $controller) => {
user = specHelper.newUser();
user._id = "unique-user-id";
userSerivce = {user: user};
userSerivce.sync = sandbox.stub();
scope = $rootScope.$new();
$controller('GroupTaskMetaActionsCtrl', {$scope: scope, User: userSerivce});
scope.task = {
group: {
assignedUsers: [],
},
};
});
});
describe('claim', () => {
beforeEach(() => {
sandbox.stub(window, 'confirm').returns(true);
});
it('adds user to assigned users of scope task ', () => {
scope.claim();
expect(scope.task.group.assignedUsers).to.contain(user._id);
});
it('syncs user tasks ', () => {
scope.claim();
expect(userSerivce.sync).to.be.calledOnce;
});
});
});
@@ -1,32 +0,0 @@
'use strict';
describe('Hall of Heroes Controller', function() {
var scope, ctrl, user, $rootScope;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function($rootScope, $controller){
user = specHelper.newUser();
scope = $rootScope.$new();
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('HallHeroesCtrl', {$scope: scope, User: {user: user}});
});
});
it('populates contributor input with selected hero id', function(){
var loadHero = sandbox.spy(scope, "loadHero");
var scrollTo = sandbox.spy(window, "scrollTo");
scope.populateContributorInput(user._id);
expect(scope._heroID).to.eql(user._id);
expect(loadHero.callCount).to.eql(1);
expect(scrollTo.callCount).to.eql(1);
});
});
@@ -1,50 +0,0 @@
'use strict';
describe('Header Controller', function() {
var scope, ctrl, user, $location, $rootScope;
beforeEach(function() {
module(function($provide) {
user = specHelper.newUser();
user._id = "unique-user-id"
$provide.value('User', {user: user});
});
inject(function(_$rootScope_, _$controller_, _$location_){
scope = _$rootScope_.$new();
$rootScope = _$rootScope_;
$location = _$location_;
// Load RootCtrl to ensure shared behaviors are loaded
_$controller_('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = _$controller_('HeaderCtrl', {$scope: scope, User: {user: user}});
});
});
context('inviteOrStartParty', function(){
beforeEach(function(){
sandbox.stub($location, 'path');
sandbox.stub($rootScope, 'openModal');
});
it('redirects to party page if user does not have a party', function(){
var group = {};
scope.inviteOrStartParty(group);
expect($location.path).to.be.calledWith("/options/groups/party");
expect($rootScope.openModal).to.not.be.called;
});
it('Opens invite-friends modal if user has a party', function(){
var group = {
type: 'party'
};
scope.inviteOrStartParty(group);
expect($rootScope.openModal).to.be.calledOnce;
expect($location.path).to.not.be.called;
});
});
});
@@ -1,73 +0,0 @@
'use strict';
describe('inbox Controller', function() {
var scope, ctrl, user, $rootScope, $controller;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function(_$rootScope_, _$controller_){
user = specHelper.newUser();
user._id = 'unique-user-id';
$rootScope = _$rootScope_;
scope = _$rootScope_.$new();
$controller = _$controller_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('InboxCtrl', {$scope: scope});
});
});
describe('copyToDo', function() {
it('when copying a user message it opens modal with information from message', function() {
scope.group = {
name: 'Princess Bride'
};
sandbox.spy($rootScope, 'openModal');
var message = {
uuid: 'the-dread-pirate-roberts',
user: 'Wesley',
text: 'As you wish'
};
scope.copyToDo(message);
expect($rootScope.openModal).to.be.calledOnce;
expect($rootScope.openModal).to.be.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
var taskText = env.t('taskTextFromInbox', {
from: message.user
});
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == taskText
}));
});
it('when copying a system message it opens modal with information from message', function() {
var modalSpy = sandbox.spy($rootScope, 'openModal');
var message = {
uuid: 'system',
text: 'Wesley attacked the ROUS in the Fire Swamp'
};
scope.copyToDo(message);
modalSpy.should.have.been.calledOnce;
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
var taskText = env.t('taskTextFromInbox', {
from: 'system'
});
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == taskText
}));
});
});
});
@@ -1,538 +0,0 @@
'use strict';
describe('Inventory Controller', function() {
var scope, ctrl, user, rootScope, shared, achievement;
beforeEach(function() {
module(function($provide) {
var mockWindow = {
confirm: function(msg) {
return true;
},
location: {search: '', pathname: '', href: ''},
};
$provide.value('$window', mockWindow);
});
inject(function($rootScope, $controller, Shared, User, $location, $window, Achievement) {
user = specHelper.newUser({
balance: 4,
items: {
gear: { owned: {} },
eggs: { Cactus: 1 },
hatchingPotions: { Base: 1 },
food: { Meat: 1 },
pets: {},
mounts: {}
},
preferences: {
suppressModals: {}
},
purchased: {
plan: {
mysteryItems: [],
},
},
});
Shared.wrap(user);
shared = Shared;
achievement = Achievement;
scope = $rootScope.$new();
rootScope = $rootScope;
User.user = user;
User.setUser(user);
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: User});
ctrl = $controller('InventoryCtrl', {$scope: scope, User: User});
});
});
it('starts without any item selected', function(){
expect(scope.selectedEgg).to.eql(null);
expect(scope.selectedPotion).to.eql(null);
expect(scope.selectedFood).to.eql(undefined);
});
it('chooses an egg', function(){
scope.chooseEgg('Cactus');
expect(scope.selectedEgg.key).to.eql('Cactus');
});
it('chooses a potion', function(){
scope.choosePotion('Base');
expect(scope.selectedPotion.key).to.eql('Base');
});
describe('Hatching Pets', function(){
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
});
it('hatches a pet', function(){
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(user.items.eggs).to.eql({Cactus: 0});
expect(user.items.hatchingPotions).to.eql({Base: 0});
expect(user.items.pets).to.eql({'Cactus-Base': 5});
expect(scope.selectedEgg).to.eql(null);
expect(scope.selectedPotion).to.eql(null);
});
it('shows a modal for pet hatching', function(){
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('hatchPet');
});
it('shows modal even if user has raised that pet to a mount', function(){
user.items.pets['Cactus-Base'] = -1;
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('hatchPet');
});
//@TODO: Fix Common hatch
xit('does not show modal if user tries to hatch a pet they own', function(){
user.items.pets['Cactus-Base'] = 5;
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(rootScope.openModal).to.not.have.been.called;
});
//@TODO: Fix Common hatch
xit('does not show modal if user tries to hatch a premium quest pet', function(){
user.items.eggs = {Snake: 1};
user.items.hatchingPotions = {Peppermint: 1};
scope.chooseEgg('Snake');
scope.choosePotion('Peppermint');
expect(rootScope.openModal).to.not.have.been.called;
});
it('does not show pet hatching modal if user has opted out', function(){
user.preferences.suppressModals.hatchPet = true;
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(rootScope.openModal).to.not.be.called;
});
it('shows beastMaster achievement modal if user has all 90 pets', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "beastMasterProgress").returns(90);
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('beastMaster');
});
it('shows triadBingo achievement modal if user has all pets twice and all mounts', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "mountMasterProgress").returns(90);
sandbox.stub(shared.count, "dropPetsCurrentlyOwned").returns(90);
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('triadBingo');
});
});
describe('Feeding and Raising Pets', function() {
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
user.items.pets = {'PandaCub-Base':5};
user.items.mounts = {'PandaCub-Base':false};
});
it('feeds a pet', function() {
scope.chooseFood('Meat');
scope.choosePet('PandaCub','Base');
expect(user.items.pets['PandaCub-Base']).to.eql(10);
});
it('gives weaker benefit when feeding inappropriate food', function() {
user.items.food.Honey = 1;
scope.chooseFood('Honey');
scope.choosePet('PandaCub','Base');
expect(user.items.pets['PandaCub-Base']).to.eql(7);
});
it('raises pet to a mount when feeding gauge maxes out', function() {
user.items.pets['PandaCub-Base'] = 45;
scope.chooseFood('Meat');
scope.choosePet('PandaCub','Base');
expect(user.items.pets['PandaCub-Base']).to.eql(-1);
expect(user.items.mounts['PandaCub-Base']).to.exist;
});
it('raises pet to a mount instantly when using a Saddle', function() {
user.items.food.Saddle = 1;
scope.chooseFood('Saddle');
scope.choosePet('PandaCub','Base');
expect(user.items.pets['PandaCub-Base']).to.eql(-1);
expect(user.items.mounts['PandaCub-Base']).to.exist;
});
it('displays mount raising modal for drop pets', function() {
user.items.food.Saddle = 1;
scope.chooseFood('Saddle');
scope.choosePet('PandaCub','Base');
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('raisePet');
});
it('displays mount raising modal for quest pets', function() {
user.items.food.Saddle = 1;
user.items.pets['Snake-Base'] = 1;
scope.chooseFood('Saddle');
scope.choosePet('Snake','Base');
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('raisePet');
});
it('displays mount raising modal for premium pets', function() {
user.items.food.Saddle = 1;
user.items.pets['TigerCub-Spooky'] = 1;
scope.chooseFood('Saddle');
scope.choosePet('TigerCub','Spooky');
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('raisePet');
});
it('shows mountMaster achievement modal if user has all 90 mounts', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "mountMasterProgress").returns(90);
scope.chooseFood('Meat');
scope.choosePet('PandaCub','Base');
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('mountMaster');
});
});
it('sells an egg', function(){
scope.chooseEgg('Cactus');
scope.sellInventory();
expect(user.items.eggs).to.eql({Cactus: 0});
expect(user.stats.gp).to.eql(3);
});
it('sells a potion', function(){
scope.choosePotion('Base');
scope.sellInventory();
expect(user.items.hatchingPotions).to.eql({Base: 0});
expect(user.stats.gp).to.eql(2);
});
it('sells food', function(){
scope.chooseFood('Meat');
scope.sellInventory();
expect(user.items.food).to.eql({Meat: 0});
expect(user.stats.gp).to.eql(1);
});
it('chooses a pet', function(){
user.items.pets['Cactus-Base'] = 5;
scope.choosePet('Cactus', 'Base');
expect(user.items.currentPet).to.eql('Cactus-Base');
});
it('purchases an egg', inject(function(Content){
scope.purchase('eggs', Content.eggs['Wolf']);
expect(user.balance).to.eql(3.25);
expect(user.items.eggs).to.eql({Cactus: 1, Wolf: 1})
}));
describe('Deselecting Items', function() {
it('deselects a food', function(){
scope.chooseFood('Meat');
scope.deselectItem();
expect(scope.selectedFood).to.eql(null);
});
it('deselects a potion', function(){
scope.choosePotion('Base');
scope.deselectItem();
expect(scope.selectedPotion).to.eql(null);
});
it('deselects a egg', function(){
scope.chooseEgg('Cactus');
scope.deselectItem();
expect(scope.selectedEgg).to.eql(null);
});
});
describe('openCardsModal', function(type, numberOfVariations) {
var cardsModalScope;
beforeEach(function() {
cardsModalScope = {};
sandbox.stub(rootScope, 'openModal');
sandbox.stub(rootScope, '$new').returns(cardsModalScope);
});
it('opens cards modal', function() {
scope.openCardsModal('valentine', 4);
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'cards'
);
});
it('instantiates a new scope for the modal', function() {
scope.openCardsModal('valentine', 4);
expect(rootScope.$new).to.be.calledOnce;
expect(cardsModalScope.cardType).to.eql('valentine');
expect(cardsModalScope.cardMessage).to.exist;
});
it('provides a card message', function() {
scope.openCardsModal('valentine', 1);
expect(cardsModalScope.cardMessage).to.eql(env.t('valentine0'));
});
it('randomly generates message from x number of messages', function() {
var possibleValues = [env.t('valentine0'), env.t('valentine1')];
scope.openCardsModal('valentine', 2);
expect(possibleValues).to.contain(cardsModalScope.cardMessage);
});
});
describe('#buyQuest', function() {
var quests, questObject;
beforeEach(inject(function(Quests) {
quests = Quests;
questObject = { key: 'whale' };
sandbox.stub(quests, 'buyQuest').returns({ then: function(res) { res(questObject); } });
}));
it('calls Quests.buyQuest', function() {
scope.buyQuest('foo');
expect(quests.buyQuest).to.be.calledOnce;
expect(quests.buyQuest).to.be.calledWith('foo');
});
it('sets selectedQuest to resolved quest object', function() {
scope.buyQuest('whale');
expect(rootScope.selectedQuest).to.eql(questObject);
});
it('opens buyQuest modal', function() {
sandbox.spy(rootScope, 'openModal');
scope.buyQuest('whale');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('buyQuest', {controller: 'InventoryCtrl'});
});
});
describe('#showQuest', function() {
var quests, questObject;
beforeEach(inject(function(Quests) {
quests = Quests;
questObject = { key: 'whale' };
sandbox.stub(quests, 'showQuest').returns({ then: function(res) { res(questObject); } });
}));
it('calls Quests.showQuest', function() {
scope.showQuest('foo');
expect(quests.showQuest).to.be.calledOnce;
expect(quests.showQuest).to.be.calledWith('foo');
});
it('sets selectedQuest to resolved quest object', function() {
scope.showQuest('whale');
expect(rootScope.selectedQuest).to.eql(questObject);
});
it('opens showQuest modal', function() {
sandbox.spy(rootScope, 'openModal');
scope.showQuest('whale');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('showQuest', {controller: 'InventoryCtrl'});
});
});
describe('#hasAllTimeTravelerItems', function() {
it('returns false if items remain for purchase with Mystic Hourglasses', function() {
expect(scope.hasAllTimeTravelerItems()).to.eql(false);
});
it('returns true if there are no items left to purchase', inject(function(Content) {
_.forEach(Content.gear.flat, function(v,item) {
if (item.indexOf('mystery') > -1) {
user.items.gear.owned[item] = true;
}
});
_.forEach(Content.timeTravelStable.pets, function(v,pet) {
user.items.pets[pet] = 5;
});
_.forEach(Content.timeTravelStable.mounts, function(v,mount) {
user.items.mounts[mount] = true;
});
expect(scope.hasAllTimeTravelerItems()).to.eql(true);
}));
});
describe('#hasAllTimeTravelerItemsOfType', function() {
it('returns false for Mystery Sets if there are sets left in the time traveler store', function() {
expect(scope.hasAllTimeTravelerItemsOfType('mystery')).to.eql(false);
});
it('returns true for Mystery Sets if there are no sets left to purchase', inject(function(Content) {
_.forEach(Content.gear.flat, function(v,item) {
if (item.indexOf('mystery') > -1) {
user.items.gear.owned[item] = true;
}
});
expect(scope.hasAllTimeTravelerItemsOfType('mystery')).to.eql(true);
}));
it('returns false for pets if user does not own all pets in the Time Travel Stable', function() {
expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(false);
});
it('returns true for pets if user owns all pets in the Time Travel Stable', inject(function(Content) {
_.forEach(Content.timeTravelStable.pets, function(v,pet) {
user.items.pets[pet] = 5;
});
expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(true);
}));
it('returns false for mounts if user does not own all mounts in the Time Travel Stable', function() {
expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(false);
});
it('returns true for mounts if user owns all mounts in the Time Travel Stable', inject(function(Content) {
_.forEach(Content.timeTravelStable.mounts, function(v,mount) {
user.items.mounts[mount] = true;
});
expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(true);
}));
});
describe('Gear search filter', function() {
var wrap = function(text) {
return {'text': function() {return text;}};
}
var toText = function(list) {
return _.map(list, function(ele) { return ele.text(); });
}
var gearByClass, gearByType;
beforeEach(function() {
scope.$digest();
gearByClass = {'raw': [wrap('kale'), wrap('sashimi')],
'cooked': [wrap('chicken'), wrap('potato')]};
gearByType = {'veg': [wrap('kale'), wrap('potato')],
'not': [wrap('chicken'), wrap('sashimi')]};
scope.gearByClass = gearByClass;
scope.gearByType = gearByType;
scope.equipmentQuery.query = 'a';
});
it('filters nothing if equipmentQuery is nothing', function() {
scope.equipmentQuery.query = '';
scope.$digest();
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken', 'potato']);
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken', 'sashimi']);
});
it('filters out gear if class gear changes', function() {
scope.$digest();
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato']);
scope.gearByClass['raw'].push(wrap('zucchini'));
scope.gearByClass['cooked'].push(wrap('pizza'));
scope.$digest();
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato', 'pizza']);
});
it('filters out gear if typed gear changes', function() {
scope.$digest();
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi']);
scope.gearByType['veg'].push(wrap('zucchini'));
scope.gearByType['not'].push(wrap('pizza'));
scope.$digest();
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi', 'pizza']);
});
it('filters out gear if filter query changes', function() {
scope.equipmentQuery.query = 'c';
scope.$digest();
expect(toText(scope.filteredGearByClass['raw'])).to.eql([]);
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken']);
expect(toText(scope.filteredGearByType['veg'])).to.eql([]);
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken']);
});
it('returns the right filtered gear', function() {
var equipment = [wrap('spicy tuna'), wrap('dragon'), wrap('rainbow'), wrap('caterpillar')];
expect(toText(scope.equipmentSearch(equipment, 'ra'))).to.eql(['dragon', 'rainbow']);
});
it('returns the right filtered gear if the source gear has unicode', function() {
// blue hat, red hat, red shield
var equipment = [wrap('藍色軟帽'), wrap('紅色軟帽'), wrap('紅色盾牌')];
// searching for 'red' gives red hat, red shield
expect(toText(scope.equipmentSearch(equipment, '紅色'))).to.eql(['紅色軟帽', '紅色盾牌']);
});
});
});
@@ -1,257 +0,0 @@
'use strict';
describe('Invite to Group Controller', function() {
var scope, ctrl, groups, user, guild, rootScope, $controller;
beforeEach(function() {
user = specHelper.newUser({
profile: { name: 'Mario' }
});
module(function($provide) {
$provide.value('User', {});
$provide.value('injectedGroup', { user: user });
});
inject(function(_$rootScope_, _$controller_, Groups) {
rootScope = _$rootScope_;
scope = _$rootScope_.$new();
$controller = _$controller_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('InviteToGroupCtrl', {$scope: scope, User: {user: user}});
groups = Groups;
});
});
describe('addEmail', function() {
it('adds blank email to email list', function() {
scope.emails = [{name: 'Mario', email: 'mario@mushroomkingdom.com'}];
scope.addEmail();
expect(scope.emails).to.eql([{name: 'Mario', email: 'mario@mushroomkingdom.com'}, {name: '', email: ''}]);
});
});
describe('addUuid', function() {
it('adds blank uuid to invitees list', function() {
scope.invitees = [{uuid: 'user1'}];
scope.addUuid();
expect(scope.invitees).to.eql([{uuid: 'user1'}, {uuid: ''}]);
});
});
describe('inviteNewUsers', function() {
var groupInvite, groupCreate;
beforeEach(function() {
scope.group = specHelper.newGroup({
type: 'party',
});
groupCreate = sandbox.stub(groups.Group, 'create');
groupInvite = sandbox.stub(groups.Group, 'invite');
});
context('if the party does not already exist', function() {
var groupResponse;
beforeEach(function() {
delete scope.group._id;
groupResponse = {data: {data: scope.group}}
});
it('saves the group if a new group is being created', function() {
groupCreate.returns(Promise.resolve(groupResponse));
scope.inviteNewUsers('uuid');
expect(groupCreate).to.be.calledOnce;
});
it('uses provided name', function() {
scope.group.name = 'test party';
groupCreate.returns(Promise.resolve(groupResponse));
scope.inviteNewUsers('uuid');
expect(groupCreate).to.be.calledWith(scope.group);
expect(scope.group.name).to.eql('test party');
});
it('names the group if no name is provided', function() {
scope.group.name = '';
groupCreate.returns(Promise.resolve(groupResponse));
scope.inviteNewUsers('uuid');
expect(groupCreate).to.be.calledWith(scope.group);
expect(scope.group.name).to.eql(env.t('possessiveParty', {name: user.profile.name}));
});
});
context('email', function() {
beforeEach(function () {
sandbox.stub(rootScope, 'hardRedirect');
});
it('invites user with emails', function(done) {
scope.emails = [
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
{name: 'Mario', email: 'mario@tmk.com'}
];
var inviteDetails = {
inviter: user.profile.name,
emails: [
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
{name: 'Mario', email: 'mario@tmk.com'}
]
};
groupInvite.returns(
Promise.resolve()
.then(function () {
expect(groupInvite).to.be.calledOnce;
expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails);
done();
})
);
scope.inviteNewUsers('email');
});
it('resets email list after sending', function(done) {
scope.emails[0].name = 'Luigi';
scope.emails[0].email = 'mario_bro@themushroomkingdom.com';
groupInvite.returns(
Promise.resolve()
.then(function () {
//We use a timeout to test items that happen after the promise is resolved
setTimeout(function(){
expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]);
done();
}, 1000);
})
);
scope.inviteNewUsers('email');
});
it('filters out blank email inputs', function() {
scope.emails = [
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
{name: 'Toad', email: ''},
{name: 'Mario', email: 'mario@tmk.com'}
];
var inviteDetails = {
inviter: user.profile.name,
emails: [
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
{name: 'Mario', email: 'mario@tmk.com'}
]
};
groupInvite.returns(
Promise.resolve()
.then(function () {
expect(groupInvite).to.be.calledOnce;
expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails);
done();
})
);
scope.inviteNewUsers('email');
});
});
context('uuid', function() {
beforeEach(function () {
sandbox.stub(rootScope, 'hardRedirect');
});
it('invites user with uuid', function(done) {
scope.invitees = [{uuid: '1234'}];
groupInvite.returns(
Promise.resolve()
.then(function () {
expect(groupInvite).to.be.calledOnce;
expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['1234'] });
done();
})
);
scope.inviteNewUsers('uuid');
});
it('invites users with uuids', function(done) {
scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}];
groupInvite.returns(
Promise.resolve()
.then(function () {
expect(groupInvite).to.be.calledOnce;
expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user2', 'user3'] });
done();
})
);
scope.inviteNewUsers('uuid');
});
it('resets invitee list after sending', function(done) {
scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}];
groupInvite.returns(
Promise.resolve()
.then(function () {
//We use a timeout to test items that happen after the promise is resolved
setTimeout(function(){
expect(scope.invitees).to.eql([{uuid: ''}]);
done();
}, 1000);
done();
})
);
scope.inviteNewUsers('uuid');
});
it('removes blank fields from being sent', function() {
scope.invitees = [{uuid: 'user1'}, {uuid: ''}, {uuid: 'user3'}];
groupInvite.returns(
Promise.resolve()
.then(function () {
expect(groupInvite).to.be.calledOnce;
expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user3'] });
done();
})
);
scope.inviteNewUsers('uuid');
});
});
context('invalid invite method', function() {
it('logs error', function() {
sandbox.stub(console, 'log');
scope.inviteNewUsers();
expect(groups.Group.invite).to.not.be.called;
expect(console.log).to.be.calledOnce;
expect(console.log).to.be.calledWith('Invalid invite method.');
});
});
});
});
@@ -1,26 +0,0 @@
'use strict';
describe('Menu Controller', function() {
describe('MenuCtrl', function(){
var scope, ctrl, user, $httpBackend, $window;
beforeEach(function(){
module(function($provide) {
$provide.value('Chat', { seenMessage: function() {} });
});
inject(function(_$httpBackend_, $rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MenuCtrl', {$scope: scope, $window: $window, User: user});
})
});
describe('clearMessage', function() {
it('is Chat.seenMessage', inject(function(Chat) {
expect(scope.clearMessages).to.eql(Chat.markChatSeen);
}));
});
});
});
@@ -1,191 +0,0 @@
'use strict';
describe('Notification Controller', function() {
var user, scope, rootScope, fakeBackend, achievement, ctrl;
beforeEach(function() {
user = specHelper.newUser();
user._id = "unique-user-id";
user.needsCron = false;
var userSync = sinon.stub().returns({
then: function then (f) { f(); }
});
let User = {
user,
readNotification: function noop () {},
readNotifications: function noop () {},
sync: userSync
};
module(function($provide) {
$provide.value('User', User);
$provide.value('Guide', {});
});
inject(function(_$rootScope_, $httpBackend, _$controller_, Achievement, Shared) {
scope = _$rootScope_.$new();
rootScope = _$rootScope_;
fakeBackend = $httpBackend;
fakeBackend.when('GET', 'partials/main.html').respond({});
achievement = Achievement;
Shared.wrap(user);
// Load RootCtrl to ensure shared behaviors are loaded
_$controller_('RootCtrl', {$scope: scope, User});
ctrl = _$controller_('NotificationCtrl', {$scope: scope, User});
});
sandbox.stub(rootScope, 'openModal');
sandbox.stub(achievement, 'displayAchievement');
});
describe('Quest Invitation modal watch', function() {
it('opens quest invitation modal', function() {
user.party.quest.RSVPNeeded = true;
delete user.party.quest.completed;
scope.$digest();
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('questInvitation', {controller:'PartyCtrl'});
});
it('does not open quest invitation modal if RSVPNeeded is not true', function() {
user.party.quest.RSVPNeeded = false;
delete user.party.quest.completed;
scope.$digest();
expect(rootScope.openModal).to.not.be.called;
});
it('does not open quest invitation modal if quest.completed contains a quest key', function() {
user.party.quest.RSVPNeeded = true;
user.party.quest.completed = "hedgebeast";
scope.$digest();
expect(rootScope.openModal).to.not.be.calledWith('questInvitation', {controller:'PartyCtrl'});
});
});
describe('Quest Completion modal watch', function() {
it('opens quest completion modal', function() {
user.party.quest.completed = "hedgebeast";
scope.$digest();
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('questCompleted', {controller:'InventoryCtrl'});
});
// Ensures that the completion modal opens before the invitation modal
it('opens quest completion modal if RSVPNeeded is true', function() {
user.party.quest.RSVPNeeded = true;
user.party.quest.completed = "hedgebeast";
scope.$digest();
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('questCompleted', {controller:'InventoryCtrl'});
});
it('does not open quest completion modal if quest.completed is null', function() {
user.party.quest.completed = null;
scope.$digest();
expect(rootScope.openModal).to.not.be.called;
});
});
describe('User challenge won notification watch', function() {
it('opens challenge won modal when a challenge-won notification is received', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'WON_CHALLENGE'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('wonChallenge');
});
it('does not open challenge won modal if no new challenge-won notification is received', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('wonChallenge');
});
});
describe('User streak achievement notification watch', function() {
it('opens streak achievement modal when a streak-achievement notification is received', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'STREAK_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('streak', {size: 'md'});
});
it('does not open streak achievement modal if no new streak-achievement notification is received', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('streak', {size: 'md'});
});
});
describe('User ultimate gear set achievement notification watch', function() {
it('opens ultimate gear set achievement modal when an ultimate-gear-achievement notification is received', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'ULTIMATE_GEAR_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('ultimateGear', {size: 'md'});
});
it('does not open ultimate gear set achievement modal if no new ultimate-gear-achievement notification is received', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('ultimateGear', {size: 'md'});
});
});
describe('User rebirth achievement notification watch', function() {
it('opens rebirth achievement modal when a rebirth-achievement notification is received', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'REBIRTH_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('rebirth');
});
it('does not open rebirth achievement modal if no new rebirth-achievement notification is received', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('rebirth');
});
});
describe('User contributor achievement notification watch', function() {
it('opens contributor achievement modal when a new-contributor-level notification is received', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'NEW_CONTRIBUTOR_LEVEL'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('contributor', {size: 'md'});
});
it('does not open contributor achievement modal if no new new-contributor-level notification is received', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('contributor', {size: 'md'});
});
});
});
@@ -1,489 +0,0 @@
'use strict';
describe("Party Controller", function() {
var scope, ctrl, user, User, questsService, groups, achievement, rootScope, $controller, deferred, party;
beforeEach(function() {
user = specHelper.newUser(),
user._id = "unique-user-id";
User = {
user: user,
sync: sandbox.spy(),
set: sandbox.spy()
};
party = specHelper.newGroup({
_id: "unique-party-id",
type: 'party',
members: ['leader-id'] // Ensure we wouldn't pass automatically.
});
module(function($provide) {
$provide.value('User', User);
});
inject(function(_$rootScope_, _$controller_, Groups, Quests, _$q_, Achievement){
rootScope = _$rootScope_;
scope = _$rootScope_.$new();
$controller = _$controller_;
groups = Groups;
questsService = Quests;
achievement = Achievement;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: User});
ctrl = $controller('PartyCtrl', {$scope: scope, User: User});
});
});
describe('initialization', function() {
var groupResponse;
function initializeControllerWithStubbedState() {
inject(function(_$state_) {
var state = _$state_;
sandbox.stub(state, 'is').returns(true);
var syncParty = sinon.stub(groups.Group, 'syncParty')
syncParty.returns(Promise.resolve(groupResponse));
var froceSyncParty = sinon.stub(groups, 'party')
froceSyncParty.returns(Promise.resolve(groupResponse));
$controller('PartyCtrl', { $scope: scope, $state: state, User: User });
expect(state.is).to.be.calledOnce;
});
};
beforeEach(function() {
sandbox.stub(achievement, 'displayAchievement');
});
context('party has 1 member', function() {
it('awards no new achievements', function() {
groupResponse = {_id: "test", type: "party", memberCount: 1};
initializeControllerWithStubbedState();
expect(User.set).to.not.be.called;
expect(achievement.displayAchievement).to.not.be.called;
});
});
context('party has 2 members', function() {
context('user does not have "Party Up" achievement', function() {
it('awards "Party Up" achievement', function(done) {
groupResponse = {_id: "test", type: "party", memberCount: 2};
initializeControllerWithStubbedState();
setTimeout(function() {
expect(User.set).to.be.calledOnce;
expect(User.set).to.be.calledWith(
{ 'achievements.partyUp': true }
);
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('partyUp');
done();
}, 1000);
});
});
});
context('party has 4 members', function() {
beforeEach(function() {
groupResponse = {_id: "test", type: "party", memberCount: 4};
});
context('user has "Party Up" but not "Party On" achievement', function() {
it('awards "Party On" achievement', function(done) {
user.achievements.partyUp = true;
initializeControllerWithStubbedState();
setTimeout(function(){
expect(User.set).to.be.calledOnce;
expect(User.set).to.be.calledWith(
{ 'achievements.partyOn': true }
);
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('partyOn');
done();
}, 1000);
});
});
context('user has neither "Party Up" nor "Party On" achievements', function() {
it('awards "Party Up" and "Party On" achievements', function(done) {
initializeControllerWithStubbedState();
setTimeout(function(){
expect(User.set).to.have.been.called;
expect(User.set).to.be.calledWith(
{ 'achievements.partyUp': true}
);
expect(User.set).to.be.calledWith(
{ 'achievements.partyOn': true}
);
expect(achievement.displayAchievement).to.have.been.called;
expect(achievement.displayAchievement).to.be.calledWith('partyUp');
expect(achievement.displayAchievement).to.be.calledWith('partyOn');
done();
}, 1000);
});
});
context('user has both "Party Up" and "Party On" achievements', function() {
it('awards no new achievements', function() {
user.achievements.partyUp = true;
user.achievements.partyOn = true;
initializeControllerWithStubbedState();
expect(User.set).to.not.be.called;
expect(achievement.displayAchievement).to.not.be.called;
});
});
});
});
describe("create", function() {
var partyStub;
beforeEach(function () {
partyStub = sinon.stub(groups.Group, "create");
partyStub.returns(Promise.resolve(party));
sinon.stub(rootScope, 'hardRedirect');
});
it("creates a new party", function() {
var group = {
type: 'party',
};
scope.create(group);
expect(partyStub).to.be.calledOnce;
//@TODO: Check user party console.log(User.user.party.id)
});
});
describe('questAccept', function() {
var sendAction;
var memberResponse;
beforeEach(function() {
scope.group = {
quest: { members: { 'user-id': true } }
};
memberResponse = {members: {another: true}};
sinon.stub(questsService, 'sendAction')
questsService.sendAction.returns(Promise.resolve(memberResponse));
});
it('calls Quests.sendAction', function() {
scope.questAccept();
expect(questsService.sendAction).to.be.calledOnce;
expect(questsService.sendAction).to.be.calledWith('quests/accept');
});
it('updates quest object with new participants list', function(done) {
scope.group.quest = {
members: { user: true, another: true }
};
setTimeout(function(){
expect(scope.group.quest).to.eql(memberResponse);
done();
}, 1000);
scope.questAccept();
});
});
describe('questReject', function() {
var memberResponse;
beforeEach(function() {
scope.group = {
quest: { members: { 'user-id': true } }
};
memberResponse = {members: {another: true}};
var sendAction = sinon.stub(questsService, 'sendAction')
sendAction.returns(Promise.resolve(memberResponse));
});
it('calls Quests.sendAction', function() {
scope.questReject();
expect(questsService.sendAction).to.be.calledOnce;
expect(questsService.sendAction).to.be.calledWith('quests/reject');
});
it('updates quest object with new participants list', function(done) {
scope.group.quest = {
members: { user: true, another: true }
};
setTimeout(function(){
expect(scope.group.quest).to.eql(memberResponse);
done();
}, 1000);
scope.questReject();
});
});
describe('questCancel', function() {
var party, cancelSpy, windowSpy, memberResponse;
beforeEach(function() {
scope.group = {
quest: { members: { 'user-id': true } }
};
memberResponse = {members: {another: true}};
sinon.stub(questsService, 'sendAction')
questsService.sendAction.returns(Promise.resolve(memberResponse));
});
it('calls Quests.sendAction when alert box is confirmed', function() {
sandbox.stub(window, "confirm").returns(true);
scope.questCancel();
expect(window.confirm).to.be.calledOnce;
expect(window.confirm).to.be.calledWith(window.env.t('sureCancel'));
expect(questsService.sendAction).to.be.calledOnce;
expect(questsService.sendAction).to.be.calledWith('quests/cancel');
});
it('does not call Quests.sendAction when alert box is not confirmed', function() {
sandbox.stub(window, "confirm").returns(false);
scope.questCancel();
expect(window.confirm).to.be.calledOnce;
expect(questsService.sendAction).to.not.be.called;
});
});
describe('questAbort', function() {
var memberResponse;
beforeEach(function() {
scope.group = {
quest: { members: { 'user-id': true } }
};
memberResponse = {members: {another: true}};
sinon.stub(questsService, 'sendAction')
questsService.sendAction.returns(Promise.resolve(memberResponse));
});
it('calls Quests.sendAction when two alert boxes are confirmed', function() {
sandbox.stub(window, "confirm", function(){return true});
scope.questAbort();
expect(window.confirm).to.be.calledTwice;
expect(window.confirm).to.be.calledWith(window.env.t('sureAbort'));
expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort'));
expect(questsService.sendAction).to.be.calledOnce;
expect(questsService.sendAction).to.be.calledWith('quests/abort');
});
it('does not call Quests.sendAction when first alert box is not confirmed', function() {
sandbox.stub(window, "confirm", function(){return false});
scope.questAbort();
expect(window.confirm).to.be.calledOnce;
expect(window.confirm).to.be.calledWith(window.env.t('sureAbort'));
expect(window.confirm).to.not.be.calledWith(window.env.t('doubleSureAbort'));
expect(questsService.sendAction).to.not.be.called;
});
it('does not call Quests.sendAction when first alert box is confirmed but second one is not', function() {
// Hack to confirm first window, but not second
// Should not be necessary when we upgrade sinon
var shouldReturn = false;
sandbox.stub(window, 'confirm', function(){
shouldReturn = !shouldReturn;
return shouldReturn;
});
scope.questAbort();
expect(window.confirm).to.be.calledTwice;
expect(window.confirm).to.be.calledWith(window.env.t('sureAbort'));
expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort'));
expect(questsService.sendAction).to.not.be.called;
});
});
describe('#questLeave', function() {
var memberResponse;
beforeEach(function() {
scope.group = {
quest: { members: { 'user-id': true } }
};
memberResponse = {members: {another: true}};
sinon.stub(questsService, 'sendAction')
questsService.sendAction.returns(Promise.resolve(memberResponse));
});
it('calls Quests.sendAction when alert box is confirmed', function() {
sandbox.stub(window, "confirm").returns(true);
scope.questLeave();
expect(window.confirm).to.be.calledOnce;
expect(window.confirm).to.be.calledWith(window.env.t('sureLeave'));
expect(questsService.sendAction).to.be.calledOnce;
expect(questsService.sendAction).to.be.calledWith('quests/leave');
});
it('does not call Quests.sendAction when alert box is not confirmed', function() {
sandbox.stub(window, "confirm").returns(false);
scope.questLeave();
expect(window.confirm).to.be.calledOnce;
questsService.sendAction.should.not.have.been.calledOnce;
});
it('updates quest object with new participants list', function(done) {
scope.group.quest = {
members: { user: true, another: true }
};
sandbox.stub(window, "confirm").returns(true);
setTimeout(function(){
expect(scope.group.quest).to.eql(memberResponse);
done();
}, 1000);
scope.questLeave();
});
});
describe('clickStartQuest', function() {
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
sandbox.stub(rootScope.$state, 'go');
});
it('opens quest modal if user has a quest', function() {
user.items.quests = {
whale: 1
};
scope.clickStartQuest();
expect(rootScope.$state.go).to.not.be.called;
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'ownedQuests',
{ controller: 'InventoryCtrl' }
);
});
it('does not open modal if user has no quests', function() {
user.items.quests = { };
scope.clickStartQuest();
expect(rootScope.openModal).to.not.be.called;
expect(rootScope.$state.go).to.be.calledOnce;
expect(rootScope.$state.go).to.be.calledWith('options.inventory.quests');
});
it('does not open modal if user had quests previously, but does not now', function() {
user.items.quests = {
whale: 0,
atom1: 0
};
scope.clickStartQuest();
expect(rootScope.openModal).to.not.be.called;
expect(rootScope.$state.go).to.be.calledOnce;
expect(rootScope.$state.go).to.be.calledWith('options.inventory.quests');
});
});
describe('#leaveOldPartyAndJoinNewParty', function() {
beforeEach(function() {
sandbox.stub(scope, 'join');
groups.data.party = { _id: 'old-party' };
var groupLeave = sandbox.stub(groups.Group, 'leave');
groupLeave.returns(Promise.resolve({}));
sandbox.stub(groups, 'party').returns({
_id: 'old-party'
});
sandbox.stub(window, 'confirm').returns(true);
});
it('does nothing if user declines confirmation', function() {
window.confirm.returns(false);
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
expect(groups.Group.leave).to.not.be.called;
})
it('leaves user\'s current party', function() {
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
expect(groups.Group.leave).to.be.calledOnce;
expect(groups.Group.leave).to.be.calledWith('old-party', false);
});
it('joins the new party', function(done) {
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
setTimeout(function() {
expect(scope.join).to.be.calledOnce;
expect(scope.join).to.be.calledWith({id: 'some-id', name: 'some-name'});
done();
}, 1000);
});
});
describe('#canEditQuest', function() {
var party;
beforeEach(function() {
party = specHelper.newGroup({
type: 'party',
leader: {},
quest: {}
});
scope.group = party;
});
it('returns false if user is not the quest leader', function() {
party.quest.leader = 'another-user';
expect(scope.canEditQuest(party)).to.eql(false);
});
it('returns true if user is quest leader', function() {
party.quest.leader = 'unique-user-id';
expect(scope.canEditQuest(party)).to.eql(true);
});
});
});
@@ -1,187 +0,0 @@
'use strict';
describe('Root Controller', function() {
var scope, rootscope, user, User, notification, ctrl, $httpBackend;
beforeEach(function () {
module(function($provide) {
$provide.value('User', {});
$provide.service('$templateCache', function () {
return {
get: function () {},
put: function () {}
}
});
});
inject(function($rootScope, $controller, _$httpBackend_, Notification) {
scope = $rootScope.$new();
scope.loginUsername = 'user';
scope.loginPassword = 'pass';
rootscope = $rootScope;
$httpBackend = _$httpBackend_;
notification = Notification;
sandbox.stub(notification, 'text');
sandbox.stub(notification, 'markdown');
user = specHelper.newUser();
User = {user: user};
User.save = sandbox.spy();
User.sync = sandbox.spy();
$httpBackend.whenGET(/partials/).respond();
ctrl = $controller('RootCtrl', {$scope: scope, User: User});
});
});
describe('contribText', function(){
it('shows contributor level text', function(){
expect(scope.contribText()).to.eql(undefined);
expect(scope.contribText(null, {npc: 'NPC'})).to.eql('NPC');
expect(scope.contribText({level: 0, text: 'Blacksmith'})).to.eql(undefined);
expect(scope.contribText({level: 1, text: 'Blacksmith'})).to.eql('Friend Blacksmith');
expect(scope.contribText({level: 2, text: 'Blacksmith'})).to.eql('Friend Blacksmith');
expect(scope.contribText({level: 3, text: 'Blacksmith'})).to.eql('Elite Blacksmith');
expect(scope.contribText({level: 4, text: 'Blacksmith'})).to.eql('Elite Blacksmith');
expect(scope.contribText({level: 5, text: 'Blacksmith'})).to.eql('Champion Blacksmith');
expect(scope.contribText({level: 6, text: 'Blacksmith'})).to.eql('Champion Blacksmith');
expect(scope.contribText({level: 7, text: 'Blacksmith'})).to.eql('Legendary Blacksmith');
expect(scope.contribText({level: 8, text: 'Blacksmith'})).to.eql('Guardian Blacksmith');
expect(scope.contribText({level: 9, text: 'Blacksmith'})).to.eql('Heroic Blacksmith');
expect(scope.contribText({level: 9, text: 'Blacksmith'}, {npc: 'NPC'})).to.eql('NPC');
});
});
describe('castEnd', function(){
var task_target, type;
beforeEach(function(){
task_target = {
id: 'task-id',
text: 'task'
};
type = 'task';
scope.spell = {
target: 'task',
key: 'fireball',
mana: 10,
text: function() { return env.t('spellWizardFireballText') },
cast: function(){}
};
rootscope.applyingAction = true;
});
context('fails', function(){
it('exits early if there is no applying action', function(){
rootscope.applyingAction = null;
expect(scope.castEnd(task_target, type)).to.be.eql('No applying action');
});
it('sends notification if target is invalid', function(){
scope.spell.target = 'not_the_same_target';
scope.castEnd(task_target, type);
notification.text.should.have.been.calledWith(window.env.t('invalidTarget'));
});
});
context('succeeds', function(){
it('sets scope.spell and rootScope.applyingAction to falsy values', function(){
scope.castEnd(task_target, type);
expect(rootscope.applyingAction).to.eql(false);
expect(scope.spell).to.eql(null);
});
it('calls $scope.spell.cast', function(){
// Kind of a hack, would prefer to use sinon.spy,
// but scope.spell gets turned to null in scope.castEnd
var spellWasCast = false;
scope.spell.cast = function(){ spellWasCast = true };
scope.castEnd(task_target, type);
expect(spellWasCast).to.eql(true);
});
it('calls cast endpoint', function() {
$httpBackend.expectPOST(/cast/).respond(201);
scope.castEnd(task_target, type);
$httpBackend.flush();
});
it('sends notification that spell was cast on task', function() {
$httpBackend.expectPOST(/cast/).respond(201);
scope.castEnd(task_target, type);
$httpBackend.flush();
expect(notification.markdown).to.be.calledOnce;
expect(notification.markdown).to.be.calledWith('You cast Burst of Flames on task.');
expect(User.sync).to.be.calledOnce;
});
it('sends notification that spell was cast on user', function() {
var user_target = {
profile: { name: 'Lefnire' }
};
scope.spell = {
target: 'user',
key: 'snowball',
mana: 0,
text: function() { return env.t('spellSpecialSnowballAuraText') },
cast: function(){}
};
$httpBackend.expectPOST(/cast/).respond(201);
scope.castEnd(user_target, 'user');
$httpBackend.flush();
expect(notification.markdown).to.be.calledOnce;
expect(notification.markdown).to.be.calledWith('You cast Snowball on Lefnire.');
expect(User.sync).to.be.calledOnce;
});
it('sends notification that spell was cast on party', function() {
var party_target = {};
scope.spell = {
target: 'party',
key: 'healAll',
mana: 25,
text: function() { return env.t('spellHealerHealAllText') },
cast: function(){}
};
$httpBackend.expectPOST(/cast/).respond(201);
scope.castEnd(party_target, 'party');
$httpBackend.flush();
expect(notification.markdown).to.be.calledOnce;
expect(notification.markdown).to.be.calledWith('You cast Blessing for the party.');
expect(User.sync).to.be.calledOnce;
});
it('sends notification that spell was cast on self', function() {
var self_target = {};
scope.spell = {
target: 'self',
key: 'stealth',
mana: 45,
text: function() { return env.t('spellRogueStealthText') },
cast: function(){}
};
$httpBackend.expectPOST(/cast/).respond(201);
scope.castEnd(self_target, 'self');
$httpBackend.flush();
expect(notification.markdown).to.be.calledOnce;
expect(notification.markdown).to.be.calledWith('You cast Stealth.');
expect(User.sync).to.be.calledOnce;
});
});
});
});
@@ -1,367 +0,0 @@
'use strict';
describe('Settings Controller', function () {
var rootScope, scope, $httpBackend, user, User, ctrl, Notification;
const actionClickEvent = {
target: document.createElement('button'),
};
beforeEach(function () {
module(function($provide) {
user = specHelper.newUser();
User = {
set: sandbox.stub(),
reroll: sandbox.stub(),
rebirth: sandbox.stub(),
releasePets: sandbox.stub(),
releaseMounts: sandbox.stub(),
releaseBoth: sandbox.stub(),
setCustomDayStart: sandbox.stub(),
user: user
};
User.user.ops = {
reroll: sandbox.stub(),
rebirth: sandbox.stub(),
releasePets: sandbox.stub(),
releaseMounts: sandbox.stub(),
releaseBoth: sandbox.stub(),
};
Notification = {
error: sandbox.stub(),
text: sandbox.stub()
};
$provide.value('Notification', Notification);
$provide.value('User', User);
$provide.value('Guide', sandbox.stub());
});
inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
scope = _$rootScope_.$new();
rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$httpBackend.whenGET(/partials/).respond();
// Load RootCtrl to ensure shared behaviors are loaded
_$controller_('RootCtrl', {$scope: scope, User: User, Notification: Notification});
ctrl = _$controller_('SettingsCtrl', {$scope: scope, User: User, Notification: Notification});
});
});
describe('#openDayStartModal', function () {
beforeEach(function () {
sandbox.stub(rootScope, 'openModal');
sandbox.stub(window, 'alert');
});
it('opens the day start modal', function () {
scope.openDayStartModal(5);
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('change-day-start', {scope: scope});
});
it('sets nextCron variable', function () {
expect(scope.nextCron).to.not.exist;
scope.openDayStartModal(5);
expect(scope.nextCron).to.exist;
});
it('calculates the next time cron will run', function () {
var fakeCurrentTime = new Date(2013, 3, 1, 3, 12).getTime();
var expectedTime = new Date(2013, 3, 1, 5, 0, 0).getTime();
sandbox.useFakeTimers(fakeCurrentTime);
scope.openDayStartModal(5);
expect(scope.nextCron).to.eq(expectedTime);
});
it('calculates the next time cron will run and adds a day if cron would have already passed', function () {
var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime();
var expectedTime = new Date(2013, 3, 2, 5, 0, 0).getTime();
sandbox.useFakeTimers(fakeCurrentTime);
scope.openDayStartModal(5);
expect(scope.nextCron).to.eq(expectedTime);
});
});
describe('#saveDayStart', function () {
it('updates user\'s custom day start', function () {
scope.dayStart = 5;
scope.saveDayStart();
expect(User.setCustomDayStart).to.be.calledWith(5);
});
});
context('Player Reroll', function () {
describe('#reroll', function () {
beforeEach(function () {
scope.clickReroll(actionClickEvent);
});
it('destroys the previous popover if it exists', function () {
sandbox.spy($.fn, 'popover');
scope.reroll(false);
expect(scope.popoverEl).to.exist;
expect($.fn.popover).to.be.calledWith('destroy');
});
it('doesn\'t call reroll when not confirmed', function () {
scope.reroll(false);
expect(user.ops.reroll).to.not.be.calledOnce;
});
it('calls reroll on the user when confirmed', function () {
sandbox.stub(rootScope.$state, 'go');
scope.reroll(true);
expect(User.reroll).to.be.calledWith({});
});
it('navigates to the tasks page when confirmed', function () {
sandbox.stub(rootScope.$state, 'go');
scope.reroll(true);
expect(rootScope.$state.go).to.be.calledWith('tasks');
});
});
describe('#clickReroll', function () {
it('displays a confirmation popover for the user', function () {
sandbox.spy($.fn, 'popover');
scope.clickReroll(actionClickEvent);
expect($.fn.popover).to.be.calledWith('destroy');
expect($.fn.popover).to.be.calledWith('show');
});
});
});
context('Player Rebirth', function () {
describe('#rebirth', function () {
beforeEach(function () {
scope.clickRebirth(actionClickEvent);
});
it('destroys the previous popover if it exists', function () {
sandbox.spy($.fn, 'popover');
scope.rebirth(false);
expect(scope.popoverEl).to.exist;
expect($.fn.popover).to.be.calledWith('destroy');
});
it('doesn\'t call rebirth when not confirmed', function () {
scope.rebirth(false);
expect(user.ops.rebirth).to.not.be.calledOnce;
});
it('calls rebirth on the user when confirmed', function () {
sandbox.stub(rootScope.$state, 'go');
scope.rebirth(true);
expect(User.rebirth).to.be.calledWith({});
});
it('navigates to tasks page when confirmed', function () {
sandbox.stub(rootScope.$state, 'go');
scope.rebirth(true);
expect(rootScope.$state.go).to.be.calledWith('tasks');
});
});
describe('#clickRebirth', function () {
it('displays a confirmation popover for the user', function () {
sandbox.spy($.fn, 'popover');
scope.clickRebirth(actionClickEvent);
expect($.fn.popover).to.be.calledWith('destroy');
expect($.fn.popover).to.be.calledWith('show');
});
});
})
context('Releasing pets and mounts', function () {
describe('#release', function () {
beforeEach(function () {
scope.clickRelease('dummy', actionClickEvent);
sandbox.stub(rootScope.$state, 'go');
});
it('destroys the previous popover if it exists', function () {
sandbox.spy($.fn, 'popover');
scope.releaseAnimals('', false);
expect($.fn.popover).to.be.calledWith('destroy');
});
it('doesn\'t call any release method if type is not provided', function () {
scope.releaseAnimals();
expect(User.releasePets).to.not.be.called;
expect(User.releaseMounts).to.not.be.called;
expect(User.releaseBoth).to.not.be.called;
});
it('doesn\'t redirect to tasks page if type is not provided', function () {
scope.releaseAnimals();
expect(rootScope.$state.go).to.not.be.called;
})
it('calls releasePets when "pets" is provided', function () {
scope.releaseAnimals('pets');
expect(User.releasePets).to.be.calledOnce;
});
it('navigates to the tasks page when "pets" is provided', function () {
scope.releaseAnimals('pets');
expect(rootScope.$state.go).to.be.calledOnce;
});
it('calls releaseMounts when "mounts" is provided', function () {
scope.releaseAnimals('mounts');
expect(User.releaseMounts).to.be.calledOnce;
});
it('navigates to the tasks page when "mounts" is provided', function () {
scope.releaseAnimals('mounts');
expect(rootScope.$state.go).to.be.calledOnce;
});
it('calls releaseBoth when "both" is provided', function () {
scope.releaseAnimals('both');
expect(User.releaseBoth).to.be.calledOnce;
});
it('navigates to the tasks page when "both" is provided', function () {
scope.releaseAnimals('both');
expect(rootScope.$state.go).to.be.calledOnce;
});
it('does not call release functions when non-applicable argument is passed in', function () {
scope.releaseAnimals('dummy');
expect(User.releasePets).to.not.be.called;
expect(User.releaseMounts).to.not.be.called;
expect(User.releaseBoth).to.not.be.called;
});
});
describe('#clickRelease', function () {
it('displays a confirmation popover for the user', function () {
sandbox.spy($.fn, 'popover');
scope.clickRelease('dummy', actionClickEvent);
expect($.fn.popover).to.be.calledWith('destroy');
expect($.fn.popover).to.be.called;
expect($.fn.popover).to.be.calledWith('show');
});
});
});
context('Validating coupons', function () {
describe('#applyCoupon', function () {
it('displays an error when an invalid coupon is applied', function () {
$httpBackend
.whenPOST('/api/v3/coupons/validate/INVALID_COUPON?userV=undefined')
.respond(200, {
success: true,
data: {
valid: false
},
notifications: [],
userV: 'undefined'
});
scope.applyCoupon('INVALID_COUPON');
$httpBackend.flush();
expect(Notification.error).to.be.called;
expect(Notification.error).to.be.calledWith(env.t('invalidCoupon'), true);
});
it('displays an confirmation when a valid coupon is applied', function () {
$httpBackend
.whenPOST('/api/v3/coupons/validate/VALID_COUPON?userV=undefined')
.respond(200, {
success: true,
data: {
valid: true
},
notifications: [],
userV: 'undefined'
});
scope.applyCoupon('VALID_COUPON');
$httpBackend.flush();
expect(Notification.error).to.not.be.called;
expect(Notification.text).to.be.calledWith('Coupon applied!');
});
});
});
context('Fixing character values', function () {
describe('#restore', function () {
var blankRestoreValues = {
stats: {
hp: 0,
exp: 0,
gp: 0,
lvl: 0,
mp: 0,
},
achievements: {
streak: 0,
},
};
it('doesn\'t update character values when level is less than 1', function () {
scope.restoreValues = blankRestoreValues;
scope.restore();
expect(User.set).to.not.be.called;
});
it('updates character values when level is at least 1', function () {
scope.restoreValues = blankRestoreValues;
scope.restoreValues.stats.lvl = 1;
scope.restore();
expect(User.set).to.be.called;
});
});
});
});
@@ -1,41 +0,0 @@
describe('Sortable Inventory Controller', () => {
let scope;
beforeEach(inject(($rootScope, $controller) => {
scope = $rootScope.$new();
$controller('SortableInventoryController', {$scope: scope});
}));
it('defaults scope.order to set', () => {
expect(scope.order).to.eql('set')
});
describe('#setOrder', () => {
it('sets sort criteria for all standard attributes', () =>{
let oldOrder = scope.order;
let attrs = [
'constitution',
'intelligence',
'perception',
'strength',
'set'
];
attrs.forEach((attribute) => {
scope.setOrder(attribute);
expect(scope.order).to.exist;
expect(scope.order).to.not.eql(oldOrder);
oldOrder = scope.order;
});
});
it('does nothing when missing sort criteria', () =>{
scope.order = null;
scope.setOrder('foooo');
expect(scope.order).to.not.exist;
});
});
});
@@ -1,76 +0,0 @@
'use strict';
describe('Tasks Controller', function() {
var $rootScope, shared, scope, user, User, ctrl;
beforeEach(function() {
user = specHelper.newUser();
User = {
user: user
};
User.deleteTask = sandbox.stub();
User.user.ops = {
deleteTask: sandbox.stub(),
};
module(function($provide) {
$provide.value('User', User);
$provide.value('Guide', {});
});
inject(function($rootScope, $controller, Shared){
scope = $rootScope.$new();
shared = Shared;
$controller('RootCtrl', {$scope: scope, User: User});
ctrl = $controller('TasksCtrl', {$scope: scope, User: User});
});
});
describe('editTask', function() {
it('is Tasks.editTask', function() {
inject(function(Tasks) {
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
// expect(scope.editTask).to.eql(Tasks.editTask);
});
});
});
describe('removeTask', function() {
var task;
beforeEach(function() {
sandbox.stub(window, 'confirm');
task = specHelper.newTodo();
});
it('asks user to confirm deletion', function() {
scope.removeTask(task);
expect(window.confirm).to.be.calledOnce;
});
it('does not remove task if not confirmed', function() {
window.confirm.returns(false);
scope.removeTask(task);
expect(User.deleteTask).to.not.be.called;
});
it('removes task', function() {
window.confirm.returns(true);
scope.removeTask(task);
expect(User.deleteTask).to.be.calledOnce;
});
});
describe('watch to updateStore', function() {
it('updates itemStore when user gear changes', function() {
sinon.stub(shared, 'updateStore').returns({item: true});
user.items.gear.owned.foo = true;
scope.$digest();
expect(scope.itemStore).to.eql({item: true});
});
});
});
@@ -1,69 +0,0 @@
'use strict';
describe('User Controller', function() {
var $rootScope, $window, User, shared, scope, ctrl, content;
beforeEach(function() {
module(function ($provide) {
var user = specHelper.newUser();
User = {user: user}
$provide.value('Guide', sandbox.stub());
$provide.value('User', User);
$provide.value('Achievement', sandbox.stub());
$provide.value('Social', sandbox.stub());
$provide.value('Shared', {
achievements: {
getAchievementsForProfile: sandbox.stub()
},
shops: {
getBackgroundShopSets: sandbox.stub()
}
});
$provide.value('Content', {
loginIncentives: sandbox.stub()
})
});
inject(function($rootScope, $controller, User, Content) {
scope = $rootScope.$new();
content = Content;
$controller('RootCtrl', { $scope: scope, User: User});
ctrl = $controller('UserCtrl', { $scope: scope, User: User, $window: $window});
});
});
describe('getProgressDisplay', function() {
beforeEach(() => {
sandbox.stub(window.env, 't');
window.env.t.onFirstCall().returns('Progress until next');
});
it('should return initial progress', function() {
scope.profile.loginIncentives = 0;
content.loginIncentives = [{
nextRewardAt: 1,
reward: true
}];
var actual = scope.getProgressDisplay();
expect(actual.trim()).to.eql('Progress until next 0/1');
});
it('should return progress between next reward and current reward', function() {
scope.profile.loginIncentives = 1;
content.loginIncentives = [{
nextRewardAt: 1,
reward: true
}, {
prevRewardAt: 0,
nextRewardAt: 2,
reward: true
}, {
prevRewardAt: 1,
nextRewardAt: 3
}];
var actual = scope.getProgressDisplay();
expect(actual.trim()).to.eql('Progress until next 0/1');
});
});
});
@@ -1,30 +0,0 @@
'use strict';
describe('focusElement Directive', function() {
var elementToFocus, scope;
beforeEach(module('habitrpg'));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.focusThisLink = false;
var element = '<input data-focus-element="focusThisLink" />';
elementToFocus = $compile(element)(scope);
scope.$digest();
}));
it('places focus on the element it is applied to when the expression it binds to evaluates to true', inject(function($timeout) {
var focusSpy = sandbox.spy();
elementToFocus.appendTo(document.body);
elementToFocus.on('focus', focusSpy);
scope.focusThisLink = true;
scope.$digest();
$timeout.flush();
expect(document.activeElement.dataset.focusElement).to.eql("focusThisLink");
expect(focusSpy).to.have.been.called;
}));
});
@@ -1,89 +0,0 @@
'use strict';
describe('fromNow Directive', function() {
var element, scope;
var fromNow = 'recently';
var diff = 0;
beforeEach(module('habitrpg'));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.message = {};
sandbox.stub(window, 'moment').returns({
fromNow: function() { return fromNow },
diff: function() { return diff }
});
element = "<p from-now></p>";
element = $compile(element)(scope);
scope.$digest();
}));
afterEach(function() {
window.moment.restore();
});
it('sets the element text to the elapsed time', function() {
expect(element.text()).to.eql('recently');
});
describe('when the elapsed time is less than an hour', function() {
beforeEach(inject(function($compile) {
fromNow = 'recently';
diff = 0;
element = $compile('<p from-now></p>')(scope);
scope.$digest();
}));
it('updates the elapsed time every minute', inject(function($interval) {
fromNow = 'later';
expect(element.text()).to.eql('recently');
$interval.flush(60001);
expect(element.text()).to.eql('later');
}));
it('moves to hourly updates after an hour', inject(function($timeout, $interval) {
diff = 61;
$timeout.flush();
$interval.flush(60001);
fromNow = 'later';
$interval.flush(60001);
expect(element.text()).to.eql('recently');
$interval.flush(3600000);
expect(element.text()).to.eql('later');
}));
});
describe('when the elapsed time is more than an hour', function() {
beforeEach(inject(function($compile) {
fromNow = 'recently';
diff = 65;
element = $compile('<p from-now></p>')(scope);
scope.$digest();
}));
it('updates the elapsed time every hour', inject(function($interval) {
fromNow = 'later';
expect(element.text()).to.eql('recently');
$interval.flush(60001);
expect(element.text()).to.eql('recently');
$interval.flush(3600000);
expect(element.text()).to.eql('later');
}));
});
});
@@ -1,35 +0,0 @@
describe('task Directive', () => {
var compile, scope, directiveElem, $modal;
beforeEach(function(){
module(function($provide) {
$modal = {
open: sandbox.spy(),
};
$provide.value('$modal', $modal);
});
inject(function($compile, $rootScope, $templateCache) {
compile = $compile;
scope = $rootScope.$new();
$templateCache.put('templates/task.html', '<div>Task</div>');
});
directiveElem = getCompiledElement();
});
function getCompiledElement(){
var element = angular.element('<task></task>');
var compiledElement = compile(element)(scope);
scope.$digest();
return compiledElement;
}
xit('opens task note modal', () => {
scope.showNoteDetails();
expect($modal.open).to.be.calledOnce;
});
})
@@ -1,33 +0,0 @@
describe('roundLargeNumbers', function() {
beforeEach(module('habitrpg'));
it('returns same number if less than 1000', inject(function(roundLargeNumbersFilter) {
for(var num = 0; num < 1000; num++) {
expect(roundLargeNumbersFilter(num)).to.eql(num);
};
}));
it('truncates number and appends "k" if number is 1000-999999', inject(function(roundLargeNumbersFilter) {
expect(roundLargeNumbersFilter(999.01)).to.eql("1.0k");
expect(roundLargeNumbersFilter(1000)).to.eql("1.0k");
expect(roundLargeNumbersFilter(3284.12)).to.eql("3.3k");
expect(roundLargeNumbersFilter(52983.99)).to.eql("53.0k");
expect(roundLargeNumbersFilter(452983.99)).to.eql("453.0k");
expect(roundLargeNumbersFilter(999999)).to.eql("1000.0k");
}));
it('truncates number and appends "m" if number is 1000000-999999999', inject(function(roundLargeNumbersFilter) {
expect(roundLargeNumbersFilter(999999.01)).to.eql("1.0m");
expect(roundLargeNumbersFilter(1000000)).to.eql("1.0m");
expect(roundLargeNumbersFilter(3284124.12)).to.eql("3.3m");
expect(roundLargeNumbersFilter(52983105.99)).to.eql("53.0m");
expect(roundLargeNumbersFilter(452983410.99)).to.eql("453.0m");
expect(roundLargeNumbersFilter(999999999)).to.eql("1000.0m");
}));
it('truncates number and appends b" if number is greater than 999999999', inject(function(roundLargeNumbersFilter) {
expect(roundLargeNumbersFilter(999999999.01)).to.eql("1.0b");
expect(roundLargeNumbersFilter(1423985738.54)).to.eql("1.4b");
}));
});
-35
View File
@@ -1,35 +0,0 @@
describe('filter', function() {
beforeEach(module('habitrpg'));
describe('gold', function() {
it('rounds down decimal values', inject(function(goldFilter) {
expect(goldFilter(10)).to.eql(10);
expect(goldFilter(10.0)).to.eql(10);
expect(goldFilter(10.1)).to.eql(10);
expect(goldFilter(10.2)).to.eql(10);
expect(goldFilter(10.3)).to.eql(10);
expect(goldFilter(10.4)).to.eql(10);
expect(goldFilter(10.5)).to.eql(10);
expect(goldFilter(10.6)).to.eql(10);
expect(goldFilter(10.7)).to.eql(10);
expect(goldFilter(10.8)).to.eql(10);
expect(goldFilter(10.9)).to.eql(10);
expect(goldFilter(11)).to.eql(11);
}));
});
describe('silver', function() {
it('converts decimal value of gold to silver', inject(function(silverFilter) {
expect(silverFilter(10)).to.be.closeTo(0, 1);
expect(silverFilter(10.01)).to.be.closeTo(1, 1);
expect(silverFilter(10.05)).to.be.closeTo(5, 1);
expect(silverFilter(10.17)).to.be.closeTo(17, 1);
expect(silverFilter(10.23)).to.be.closeTo(23, 1);
expect(silverFilter(10.25)).to.be.closeTo(25, 1);
expect(silverFilter(10.53)).to.be.closeTo(53, 1);
expect(silverFilter(10.75)).to.be.closeTo(75, 1);
expect(silverFilter(10.99)).to.be.closeTo(99, 1);
}));
});
});
@@ -1,94 +0,0 @@
'use strict';
describe('Task Ordering Filters', function() {
var filter, orderBySpy;
beforeEach(function() {
orderBySpy = sandbox.spy();
module(function($provide) {
$provide.value('orderByFilter', orderBySpy);
});
inject(function($rootScope, $filter) {
filter = $filter;
});
});
describe('conditionalOrderBy', function() {
describe('when the predicate is true', function() {
it('delegates the arguments to the orderBy filter', function() {
filter('conditionalOrderBy')('array', true, 'sortPredicate', 'reverseOrder');
expect(orderBySpy).to.have.been.calledWith('array','sortPredicate','reverseOrder');
});
});
describe('when the predicate is false', function() {
it('returns the initial array', function() {
expect(filter('conditionalOrderBy')([1,2,3], false)).to.eql([1,2,3]);
});
});
});
describe('filterByTaskInfo', function () {
it('returns undefined when no input given', function () {
expect(filter('filterByTaskInfo')()).to.eql(undefined);
});
it('returns all tasks if term is not a string', function () {
var tasks = [1, 2, 3];
expect(filter('filterByTaskInfo')(tasks, undefined)).to.eql(tasks);
expect(filter('filterByTaskInfo')(tasks, [])).to.eql(tasks);
expect(filter('filterByTaskInfo')(tasks, new Date())).to.eql(tasks);
});
it('returns tasks if term is an empty string', function () {
var tasks = [1, 2, 3];
expect(filter('filterByTaskInfo')(tasks, '')).to.eql(tasks);
});
it('filters items by text', function () {
var tasks = [
{ text: 'foo' },
{ text: 'some text that contains foo' },
{ text: 'some text that should not be matched' }
];
expect(filter('filterByTaskInfo')(tasks, 'foo')).to.eql([tasks[0], tasks[1]]);
});
it('filters items by notes', function () {
var tasks = [
{ text: 'some text', notes: 'foo' },
{ text: 'some text', notes: 'a note that contains foo' },
{ text: 'some text', notes: 'some text' },
{ text: 'some text' }
];
expect(filter('filterByTaskInfo')(tasks, 'foo')).to.eql([tasks[0], tasks[1]]);
});
it('filters items by checklists', function () {
var tasks = [
{ text: 'foo' },
{ text: 'foo', notes: 'bar', checklist: [ {text: "checkListToFind"} ] },
{ text: 'foo', notes: 'bar', checklist: [ {text: "checkListToNotFind"} ] }
];
expect(filter('filterByTaskInfo')(tasks, 'checkListToFind')).to.eql([tasks[1]]);
});
it('only includes task once, even with multiple matches in checklist', function() {
var tasks = [
{
text: 'foo', notes: 'bar', checklist: [
{text: "checkListToFind"},
{text: "checkListToFind"},
{text: "checkListToFind"}
]
}
];
expect(filter('filterByTaskInfo')(tasks, 'checkListToFind')).to.eql([tasks[0]]);
});
});
});
@@ -1,20 +0,0 @@
describe('timezoneOffsetToUtc', function() {
beforeEach(module('habitrpg'));
it('formats the timezone offset with a - sign if the offset is positive', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(90)).to.eql('UTC-1:30');
}));
it('formats the timezone offset with a + sign if the offset is negative', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(-525)).to.eql('UTC+8:45');
}));
it('prepends the minutes with a 0 if the minute the offset is less than 10', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(60)).to.eql('UTC-1:00');
}));
it('returns the string UTC+0:00 if the offset is 0', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(0)).to.eql('UTC+0:00');
}));
});
-98
View File
@@ -1,98 +0,0 @@
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function karmaConfig (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
// list of files / patterns to load in the browser
files: [
'../../../website/client-old/bower_components/jquery/dist/jquery.js',
'../../../website/client-old/bower_components/pnotify/jquery.pnotify.js',
'../../../website/client-old/bower_components/angular/angular.js',
'../../../website/client-old/bower_components/angular-loading-bar/build/loading-bar.min.js',
'../../../website/client-old/bower_components/angular-resource/angular-resource.min.js',
'../../../website/client-old/bower_components/hello/dist/hello.all.min.js',
'../../../website/client-old/bower_components/angular-sanitize/angular-sanitize.js',
'../../../website/client-old/bower_components/bootstrap/dist/js/bootstrap.js',
'../../../website/client-old/bower_components/angular-bootstrap/ui-bootstrap.js',
'../../../website/client-old/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'../../../website/client-old/bower_components/angular-ui-router/release/angular-ui-router.js',
'../../../website/client-old/bower_components/angular-filter/dist/angular-filter.js',
'../../../website/client-old/bower_components/angular-ui/build/angular-ui.js',
'../../../website/client-old/bower_components/angular-ui-utils/ui-utils.min.js',
'../../../website/client-old/bower_components/Angular-At-Directive/src/at.js',
'../../../website/client-old/bower_components/Angular-At-Directive/src/caret.js',
'../../../website/client-old/bower_components/angular-mocks/angular-mocks.js',
'../../../website/client-old/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
'../../../website/client-old/bower_components/select2/select2.js',
'../../../website/client-old/bower_components/angular-ui-select2/src/select2.js',
'../../../website/client-old/bower_components/habitica-markdown/dist/habitica-markdown.min.js',
'../../../website/client-old/js/habitrpg-shared.js',
'../../../test/client-old/spec/mocks/**/*.js',
'../../../website/client-old/js/env.js',
'../../../website/client-old/js/app.js',
'../../../website/client-old/js/config.js',
'../../../website/client-old/js/services/**/*.js',
'../../../website/client-old/js/filters/**/*.js',
'../../../website/client-old/js/directives/**/*.js',
'../../../website/client-old/js/controllers/**/*.js',
'../../../website/client-old/js/components/**/*.js',
'../../../test/client-old/spec/specHelper.js',
'../../../test/client-old/spec/**/*.js',
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
preprocessors: {
'../../../website/client-old/js/**/*.js': ['coverage'],
'../../../test/**/*.js': ['babel'],
},
coverageReporter: {
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' },
],
dir: '../../../coverage/karma',
},
// Enable mocha-style reporting, for better test visibility
reporters: ['mocha', 'coverage'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false,
});
};
-5
View File
@@ -1,5 +0,0 @@
var sandbox = sinon.sandbox.create();
afterEach(function() {
sandbox.restore();
});
@@ -1,8 +0,0 @@
'use strict'
var analyticsMock = {
login: sandbox.spy(),
register: sandbox.spy(),
updateUser: sandbox.spy(),
track: sandbox.spy()
};
@@ -1,55 +0,0 @@
'use strict';
describe('achievementServices', function() {
var achievementService, rootScope;
beforeEach(function() {
rootScope = { 'openModal': sandbox.stub() };
module(function($provide) {
$provide.value('$rootScope', rootScope);
});
inject(function(Achievement) {
achievementService = Achievement;
});
});
describe('#displayAchievement', function() {
it('passes given achievement name to openModal', function() {
achievementService.displayAchievement('beastMaster');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('achievements/beastMaster');
});
it('calls openModal with UserCtrl and small modal size if no other size is given', function() {
achievementService.displayAchievement('test');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'sm' }
);
});
it('calls openModal with UserCtrl and specified modal size if one is given', function() {
achievementService.displayAchievement('test', {size: 'md'});
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'md' }
);
});
it('calls openModal with UserCtrl and default \'sm\' size if invalid size is given', function() {
achievementService.displayAchievement('test', {size: 'INVALID_SIZE'});
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'sm' }
);
});
});
});
@@ -1,282 +0,0 @@
'use strict';
describe('Analytics Service', function () {
var analytics, user, clock;
beforeEach(function() {
clock = sandbox.useFakeTimers();
sandbox.stub(window, 'refresher', function(){return true});
user = specHelper.newUser({
contributor: {},
purchased: { plan: {} },
});
user.flags.tour = { intro: null };
module(function($provide) {
$provide.value('User', {user: user});
});
inject(function(Analytics) {
analytics = Analytics;
});
});
context('functions', function() {
describe('register', function() {
beforeEach(function() {
sandbox.stub(amplitude, 'setUserId');
sandbox.stub(window, 'ga');
});
it('sets up user with Amplitude', function() {
analytics.register();
clock.tick();
expect(amplitude.setUserId).to.have.been.calledOnce;
expect(amplitude.setUserId).to.have.been.calledWith(user._id);
});
it('sets up user with Google Analytics', function() {
analytics.register();
clock.tick();
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('set', {userId: user._id});
});
});
describe('login', function() {
beforeEach(function() {
sandbox.stub(amplitude, 'setUserId');
sandbox.stub(window, 'ga');
});
it('sets up tracking for amplitude', function() {
analytics.login();
clock.tick();
expect(amplitude.setUserId).to.have.been.calledOnce;
expect(amplitude.setUserId).to.have.been.calledWith(user._id);
});
it('sets up tracking for google analytics', function() {
analytics.login();
clock.tick();
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('set', {userId: user._id});
});
});
describe('track', function() {
beforeEach(function() {
sandbox.stub(amplitude, 'logEvent');
sandbox.stub(window, 'ga');
});
context('successful tracking', function() {
it('tracks a simple user action with Amplitude', function() {
var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'};
analytics.track(properties);
clock.tick();
expect(amplitude.logEvent).to.have.been.calledOnce;
expect(amplitude.logEvent).to.have.been.calledWith('cron', properties);
});
it('tracks a simple user action with Google Analytics', function() {
var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'};
analytics.track(properties);
clock.tick();
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('send', properties);
});
it('tracks a user action with additional properties in Amplitude', function() {
var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'};
analytics.track(properties);
clock.tick();
expect(amplitude.logEvent).to.have.been.calledOnce;
expect(amplitude.logEvent).to.have.been.calledWith('cron', properties);
});
it('tracks a user action with additional properties in google analytics', function() {
var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'};
analytics.track(properties);
clock.tick();
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('send', properties);
});
});
context('unsuccessful tracking', function() {
beforeEach(function() {
sandbox.stub(console, 'log');
});
context('events without required properties', function() {
beforeEach(function(){
analytics.track('action');
analytics.track({'hitType':'pageview','eventCategory':'green'});
analytics.track({'hitType':'pageview','eventAction':'eat'});
analytics.track({'eventCategory':'green','eventAction':'eat'});
analytics.track({'hitType':'pageview'});
analytics.track({'eventCategory':'green'});
analytics.track({'eventAction':'eat'});
clock.tick();
});
it('logs errors to console', function() {
expect(console.log.callCount).to.eql(7);
});
it('does not call out to Amplitude', function() {
expect(amplitude.logEvent).to.not.be.called;
});
it('does not call out to Google Analytics', function() {
expect(ga).to.not.be.called;
});
});
context('incorrect hit type', function() {
beforeEach(function() {
analytics.track({'hitType':'moogly','eventCategory':'green','eventAction':'eat'});
clock.tick();
});
it('logs error to console', function () {
expect(console.log).to.have.been.calledOnce;
});
it('does not call out to Amplitude', function() {
expect(amplitude.logEvent).to.not.be.called;
});
it('does not call out to Google Analytics', function() {
expect(ga).to.not.be.called;
});
});
});
});
describe('updateUser', function() {
beforeEach(function() {
sandbox.stub(amplitude, 'setUserProperties');
sandbox.stub(window, 'ga');
});
context('properties argument provided', function(){
var properties = {'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'};
var expectedProperties = _.cloneDeep(properties);
expectedProperties.UUID = 'unique-user-id';
expectedProperties.Class = 'wizard';
expectedProperties.Experience = 35;
expectedProperties.Gold = 43;
expectedProperties.Health = 48;
expectedProperties.Level = 24;
expectedProperties.Mana = 41;
expectedProperties.tutorialComplete = false;
expectedProperties["Number Of Tasks"] = {
habits: 1,
dailys: 1,
todos: 1,
rewards: 1
};
expectedProperties.balance = 12;
expectedProperties.balanceGemAmount = 48;
beforeEach(function() {
user._id = 'unique-user-id';
user.stats.class = 'wizard';
user.stats.exp = 35.7;
user.stats.gp = 43.2;
user.stats.hp = 47.8;
user.stats.lvl = 24;
user.stats.mp = 41;
user.flags.tour.intro = 3;
user.habits = [{_id: 'habit'}];
user.dailys = [{_id: 'daily'}];
user.todos = [{_id: 'todo'}];
user.rewards = [{_id: 'reward'}];
user.balance = 12;
analytics.updateUser(properties);
clock.tick();
});
it('calls Amplitude with provided properties and select user info', function() {
expect(amplitude.setUserProperties).to.have.been.calledOnce;
expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties);
});
it('calls Google Analytics with provided properties and select user info', function() {
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('set', expectedProperties);
});
});
context('no properties argument provided', function() {
var expectedProperties = {
UUID: 'unique-user-id',
Class: 'wizard',
Experience: 35,
Gold: 43,
Health: 48,
Level: 24,
Mana: 41,
contributorLevel: 1,
subscription: 'unique-plan-id',
tutorialComplete: true,
"Number Of Tasks": {
todos: 1,
dailys: 1,
habits: 1,
rewards: 1
},
balance: 12,
balanceGemAmount: 48
};
beforeEach(function() {
user._id = 'unique-user-id';
user.stats.class = 'wizard';
user.stats.exp = 35.7;
user.stats.gp = 43.2;
user.stats.hp = 47.8;
user.stats.lvl = 24;
user.stats.mp = 41;
user.contributor.level = 1;
user.purchased.plan.planId = 'unique-plan-id';
user.flags.tour.intro = -2;
user.habits = [{_id: 'habit'}];
user.dailys = [{_id: 'daily'}];
user.todos = [{_id: 'todo'}];
user.rewards = [{_id: 'reward'}];
user.balance = 12;
analytics.updateUser();
clock.tick();
});
it('calls Amplitude with select user info', function() {
expect(amplitude.setUserProperties).to.have.been.calledOnce;
expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties);
});
it('calls Google Analytics with select user info', function() {
expect(ga).to.have.been.calledOnce;
expect(ga).to.have.been.calledWith('set', expectedProperties);
});
});
});
});
});
@@ -1,88 +0,0 @@
'use strict';
describe('challengeServices', function() {
var $httpBackend, $http, challenges, user;
var apiV3Prefix = '/api/v3';
beforeEach(function() {
module(function($provide) {
$provide.value('User', {user:user});
});
inject(function(_$httpBackend_, Challenges, User) {
$httpBackend = _$httpBackend_;
challenges = Challenges;
user = User;
user.sync = function(){};
});
});
it('calls create challenge endpoint', function() {
$httpBackend.expectPOST(apiV3Prefix + '/challenges').respond({});
challenges.createChallenge();
$httpBackend.flush();
});
it('calls join challenge endpoint', function() {
var challengeId = 1;
$httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/join').respond({});
challenges.joinChallenge(challengeId);
$httpBackend.flush();
});
it('calls leave challenge endpoint', function() {
var challengeId = 1;
$httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/leave').respond({});
challenges.leaveChallenge(challengeId);
$httpBackend.flush();
});
it('calls get user challenges endpoint', function() {
$httpBackend.expectGET(apiV3Prefix + '/challenges/user').respond({});
challenges.getUserChallenges();
$httpBackend.flush();
});
it('calls get group challenges endpoint', function() {
var groupId = 1;
$httpBackend.expectGET(apiV3Prefix + '/challenges/groups/' + groupId).respond({});
challenges.getGroupChallenges(groupId);
$httpBackend.flush();
});
it('calls get challenge endpoint', function() {
var challengeId = 1;
$httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId).respond({});
challenges.getChallenge(challengeId);
$httpBackend.flush();
});
it('calls export challenge to csv endpoint', function() {
var challengeId = 1;
$httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId + '/export/csv').respond({});
challenges.exportChallengeCsv(challengeId);
$httpBackend.flush();
});
it('calls update challenge endpoint', function() {
var challengeId = 1;
$httpBackend.expectPUT(apiV3Prefix + '/challenges/' + challengeId).respond({});
challenges.updateChallenge(challengeId);
$httpBackend.flush();
});
it('calls delete challenge endpoint', function() {
var challengeId = 1;
$httpBackend.expectDELETE(apiV3Prefix + '/challenges/' + challengeId).respond({});
challenges.deleteChallenge(challengeId);
$httpBackend.flush();
});
it('calls select challenge winner endpoint', function() {
var challengeId = 1;
var winnerId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId).respond({});
challenges.selectChallengeWinner(challengeId, winnerId);
$httpBackend.flush();
});
});
@@ -1,73 +0,0 @@
'use strict';
describe('chatServices', function() {
var $httpBackend, $http, chat, user;
var apiV3Prefix = '/api/v3';
beforeEach(function() {
module(function($provide) {
$provide.value('User', {user:user});
});
inject(function(_$httpBackend_, Chat, User) {
$httpBackend = _$httpBackend_;
chat = Chat;
user = User;
user.sync = function(){};
});
});
it('calls get chat endpoint', function() {
var groupId = 1;
$httpBackend.expectGET(apiV3Prefix + '/groups/' + groupId + '/chat').respond({});
chat.getChat(groupId);
$httpBackend.flush();
});
it('calls get chat endpoint', function() {
var groupId = 1;
var message = "test message";
$httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat').respond({});
chat.postChat(groupId, message);
$httpBackend.flush();
});
it('calls delete chat endpoint', function() {
var groupId = 1;
var chatId = 2;
$httpBackend.expectDELETE(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId).respond({});
chat.deleteChat(groupId, chatId);
$httpBackend.flush();
});
it('calls like chat endpoint', function() {
var groupId = 1;
var chatId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like').respond({});
chat.like(groupId, chatId);
$httpBackend.flush();
});
it('calls flag chat endpoint', function() {
var groupId = 1;
var chatId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag').respond({});
chat.flagChatMessage(groupId, chatId);
$httpBackend.flush();
});
it('calls clearflags chat endpoint', function() {
var groupId = 1;
var chatId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags').respond({});
chat.clearFlagCount(groupId, chatId);
$httpBackend.flush();
});
it('calls chat seen endpoint', function() {
var groupId = 1;
$httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/seen').respond({});
chat.markChatSeen(groupId);
$httpBackend.flush();
});
});
@@ -1,200 +0,0 @@
'use strict';
describe('groupServices', function() {
var $httpBackend, $http, groups, user, $rootScope;
var groupApiUrlPrefix = '/api/v3/groups';
beforeEach(function() {
module(function($provide) {
user = specHelper.newUser();
user._id = "unique-user-id"
user.party._id = 'unique-party-id';
user.sync = function(){};
$provide.value('User', {user: user});
});
inject(function(_$httpBackend_, _$rootScope_, Groups, User) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$rootScope.openModal = function() {}
groups = Groups;
});
});
it('calls get groups', function() {
$httpBackend.expectGET(groupApiUrlPrefix).respond({});
groups.Group.getGroups();
$httpBackend.flush();
});
it('calls get group', function() {
var gid = 1;
$httpBackend.expectGET(groupApiUrlPrefix + '/' + gid).respond({});
groups.Group.get(gid);
$httpBackend.flush();
});
it('calls party endpoint', function() {
var groupId = '1234';
var groupResponse = {data: {_id: groupId}};
$httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse);
$httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({});
$httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({});
$httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({});
groups.Group.syncParty();
$httpBackend.flush();
});
it('calls create endpoint', function() {
$httpBackend.expectPOST(groupApiUrlPrefix).respond({});
groups.Group.create({});
$httpBackend.flush();
});
it('calls update group', function() {
var gid = 1;
var groupDetails = { _id: gid };
$httpBackend.expectPUT(groupApiUrlPrefix + '/' + gid).respond({});
groups.Group.update(groupDetails);
$httpBackend.flush();
});
it('calls join group', function() {
var gid = 1;
$httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/join').respond({});
groups.Group.join(gid);
$httpBackend.flush();
});
it('calls reject invite group', function() {
var gid = 1;
$httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/reject-invite').respond({});
groups.Group.rejectInvite(gid);
$httpBackend.flush();
});
it('calls invite group', function() {
var gid = 1;
$httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/invite').respond({});
groups.Group.invite(gid, [], []);
$httpBackend.flush();
});
it('calls party endpoint when party is not cached', function() {
var groupId = '1234';
var groupResponse = {data: {_id: groupId}};
$httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse);
$httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({});
$httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({});
$httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({});
groups.party();
$httpBackend.flush();
});
it('returns party if cached', function (done) {
var uid = 'abc';
var party = {
_id: uid,
};
groups.data.party = party;
groups.party()
.then(function (result) {
expect(result).to.eql(party);
done();
});
$httpBackend.flush();
});
it('calls tavern endpoint when tavern is not cached', function() {
$httpBackend.expectGET(groupApiUrlPrefix + '/habitrpg').respond({});
groups.tavern();
$httpBackend.flush();
});
it('returns tavern if cached', function (done) {
var uid = 'abc';
var tavern = {
_id: uid,
};
groups.data.tavern = tavern;
groups.tavern()
.then(function (result) {
expect(result).to.eql(tavern);
done();
});
$httpBackend.flush();
});
it('calls public guilds endpoint', function() {
$httpBackend.expectGET(groupApiUrlPrefix + '?type=publicGuilds').respond([]);
groups.publicGuilds();
$httpBackend.flush();
});
it('returns public guilds if cached', function (done) {
var uid = 'abc';
var publicGuilds = [
{_id: uid},
];
groups.data.publicGuilds = publicGuilds;
groups.publicGuilds()
.then(function (result) {
expect(result).to.eql(publicGuilds);
done();
});
$httpBackend.flush();
});
it('calls my guilds endpoint', function() {
$httpBackend.expectGET(groupApiUrlPrefix + '?type=guilds').respond([]);
groups.myGuilds();
$httpBackend.flush();
});
it('returns my guilds if cached', function (done) {
var uid = 'abc';
var myGuilds = [
{_id: uid},
];
groups.data.myGuilds = myGuilds;
groups.myGuilds()
.then(function (myGuilds) {
expect(myGuilds).to.eql(myGuilds);
done();
});
$httpBackend.flush()
});
it('sets a "sendInviteText" property on a party to "Send Invitations"', function() {
var sendInviteText = window.env.t('sendInvitations');
var party = {
type: 'party',
data: {
_id: '1234',
},
};
groups.inviteOrStartParty(party);
expect(party.sendInviteText).to.eql(sendInviteText);
});
it('sets a "sendInviteText" proptery on a guild to "Send Invitations +$3.00/month/user"', function() {
var sendInviteText = window.env.t('sendInvitations');
var guild = {
type: 'guild',
data: {
_id: '12345',
},
purchased: {
plan: {
customerId: '123',
},
},
};
groups.inviteOrStartParty(guild);
expect(guild.sendInviteText).to.eql(sendInviteText + window.env.t('groupAdditionalUserCost'));
});
});
@@ -1,118 +0,0 @@
'use strict';
describe('memberServices', function() {
var $httpBackend, members;
var apiV3Prefix = '/api/v3';
beforeEach(inject(function (_$httpBackend_, Members) {
$httpBackend = _$httpBackend_;
members = Members;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('has no members at the beginning', function() {
expect(members.members).to.be.an('object');
expect(members.members).to.eql({});
expect(members.selectedMember).to.be.undefined;
});
it('calls fetch member', function() {
var memberId = 1;
var memberUrl = apiV3Prefix + '/members/' + memberId;
$httpBackend.expectGET(memberUrl).respond({});
members.fetchMember(memberId);
$httpBackend.flush();
});
it('calls get group members', function() {
var groupId = 1;
var memberUrl = apiV3Prefix + '/groups/' + groupId + '/members';
$httpBackend.expectGET(memberUrl).respond({});
members.getGroupMembers(groupId);
$httpBackend.flush();
});
it('calls get group invites', function() {
var groupId = 1;
var memberUrl = apiV3Prefix + '/groups/' + groupId + '/invites';
$httpBackend.expectGET(memberUrl).respond({});
members.getGroupInvites(groupId);
$httpBackend.flush();
});
it('calls get challenge members', function() {
var challengeId = 1;
var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members';
$httpBackend.expectGET(memberUrl).respond({});
members.getChallengeMembers(challengeId);
$httpBackend.flush();
});
it('calls get challenge members progress', function() {
var challengeId = 1;
var memberId = 2;
var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId;
$httpBackend.expectGET(memberUrl).respond({});
members.getChallengeMemberProgress(challengeId, memberId);
$httpBackend.flush();
});
describe('addToMembersList', function() {
it('adds member to members object', function() {
var member = { _id: 'user_id' };
members.addToMembersList(member, members);
expect(members.members).to.eql({
user_id: { _id: 'user_id' }
});
});
});
describe('selectMember', function() {
it('fetches member if not already in cache', function(done) {
var uid = 'abc';
var memberResponse = {
data: {_id: uid},
}
$httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse);
members.selectMember(uid)
.then(function () {
expect(members.selectedMember._id).to.eql(uid);
expect(members.members).to.have.property(uid);
done();
});
$httpBackend.flush();
});
it('fetches member if member data in cache is incomplete', function(done) {
var uid = 'abc';
members.members = {
abc: { _id: 'abc', items: {} }
}
var memberResponse = {
data: {_id: uid},
}
$httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse);
members.selectMember(uid)
.then(function () {
expect(members.selectedMember._id).to.eql(uid);
expect(members.members).to.have.property(uid);
done();
});
$httpBackend.flush();
});
it('gets member from cache if member has a weapons object', function() {
var uid = 'abc';
members.members[uid] = { _id: uid, items: { weapon: {} } };
members.selectMember(uid, function(){
expect(members.selectedMember._id).to.eql(uid);
expect(members.members).to.have.property(uid);
});
});
});
});
@@ -1,202 +0,0 @@
'use strict';
describe('notificationServices', function() {
var notification;
beforeEach(function() {
sandbox.stub($, 'pnotify', function(){
return { click: function(){}}
});
module(function($provide){
$provide.value('User', {});
});
inject(function(Notification) {
notification = Notification;
});
});
it('notifies coins amount', function() {
var SILVER_COIN = "<span class='notification-icon shop_silver'></span>";
var GOLD_COIN = "<span class='notification-icon shop_gold'></span>";
expect(notification.coins(0)).to.not.exist;
expect(notification.coins(0.01)).to.eql("1 " + SILVER_COIN);
expect(notification.coins(0.1)).to.eql("10 " + SILVER_COIN);
expect(notification.coins(1)).to.eql("1 " + GOLD_COIN);
expect(notification.coins(12.34)).to.eql("12 " + GOLD_COIN +" 33 " + SILVER_COIN);
});
it('sends crit notification', function() {
notification.crit(5);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('crit');
expect(arg.text).to.eql('Critical Hit! Bonus: 5%');
expect(arg.icon).to.eql('glyphicon glyphicon-certificate');
});
it('sends drop notification for unspecified item', function() {
notification.drop('msg');
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('drop');
expect(arg.text).to.eql('msg');
expect(arg.icon).to.eql(false);
});
it('sends drop notification for Egg', function() {
var item = { type: 'Egg', key: 'wolf' };
notification.drop('msg', item);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('drop');
expect(arg.text).to.eql('msg');
expect(arg.icon).to.eql('Pet_Egg_wolf');
});
it('sends drop notification for Hatching Potion', function() {
var item = { type: 'HatchingPotion', key: 'red' };
notification.drop('msg', item);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('drop');
expect(arg.text).to.eql('msg');
expect(arg.icon).to.eql('Pet_HatchingPotion_red');
});
it('sends drop notification for Food', function() {
var item = { type: 'Food', key: 'meat' };
notification.drop('msg', item);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('drop');
expect(arg.text).to.eql('msg');
expect(arg.icon).to.eql('Pet_Food_meat');
});
it('does not send exp notification if val < -50', function() {
notification.exp(-51);
expect($.pnotify).to.not.have.been.called;
});
it('sends exp notification if val >= -50', function() {
notification.exp(50);
notification.exp(0);
notification.exp(-50);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledThrice;
expect(arg.type).to.eql('xp');
expect(arg.text).to.eql('+ 50 Experience');
expect(arg.icon).to.eql('glyphicon glyphicon-star');
});
it('sends exp notification with rounded value', function() {
notification.exp(50.23333);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('xp');
expect(arg.text).to.eql('+ 50.2 Experience');
expect(arg.icon).to.eql('glyphicon glyphicon-star');
});
it('sends error notification', function() {
notification.error('there was an error');
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('danger');
expect(arg.text).to.eql('there was an error');
expect(arg.icon).to.eql('glyphicon glyphicon-exclamation-sign');
});
it('sends gp gained notification', function() {
notification.gp(50, 4);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('gp');
expect(arg.text).to.eql('+ 46 <span class=\'notification-icon shop_gold\'></span>');
expect(arg.icon).to.eql(false);
});
it('sends hp notification', function() {
notification.hp(10);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('hp');
expect(arg.text).to.eql('+ 10 Health');
expect(arg.icon).to.eql('glyphicon glyphicon-heart');
});
it('sends level up notification', function() {
notification.lvl(10);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('lvl');
expect(arg.text).to.eql('Level Up!');
expect(arg.icon).to.eql('glyphicon glyphicon-chevron-up');
});
it('sends markdown parsed notification', function() {
notification.markdown(":smile: - task name");
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('info');
expect(arg.text).to.eql('<p><img class="habitica-emoji" style="height: 1.5em; width: 1.5em" src="https://s3.amazonaws.com/habitica-assets/cdn/emoji/smile.png" alt="smile"> - task name</p>\n');
expect(arg.icon).to.eql(false);
});
it('does not send markdown notification if no text is given', function() {
notification.markdown();
expect($.pnotify).to.not.have.been.called;
});
it('sends mp notification', function() {
notification.mp(10);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('mp');
expect(arg.text).to.eql('+ 10 Mana');
expect(arg.icon).to.eql('glyphicon glyphicon-fire');
});
it('sends streak notification', function() {
notification.streak(10);
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('streak');
expect(arg.text).to.eql('Streak Achievements: 10');
expect(arg.icon).to.eql('glyphicon glyphicon-repeat');
});
it('sends text notification', function() {
notification.text('task name');
var arg = $.pnotify.args[0][0];
expect($.pnotify).to.have.been.calledOnce;
expect(arg.type).to.eql('info');
expect(arg.text).to.eql('task name');
expect(arg.icon).to.eql(false);
});
it('does not send text notification if no text is given', function() {
notification.text();
expect($.pnotify).to.not.have.been.called;
});
});
@@ -1,435 +0,0 @@
'use strict';
describe('Quests Service', function() {
var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy, state;
beforeEach(function() {
user = specHelper.newUser();
user.ops = {
buyQuest: sandbox.spy()
};
user.party._id = 'unique-party-id';
user.achievements.quests = {};
quest = {lvl:20};
module(function($provide) {
$provide.value('User', {sync: sinon.stub(), user: user});
});
inject(function(Quests, Groups, Content, _$state_) {
questsService = Quests;
groupsService = Groups;
content = Content;
state = _$state_;
});
sandbox.stub(groupsService, 'inviteOrStartParty');
sandbox.stub(window,'confirm');
sandbox.stub(window,'alert');
resolveSpy = sandbox.spy();
rejectSpy = sandbox.spy();
});
describe('#lockQuest', function() {
it('locks quest when user does not meet level requirement', function() {
user.stats.lvl = 15;
expect(questsService.lockQuest(quest)).to.be.ok;
});
it('does not lock quest if we ignore level requirement', function() {
user.stats.lvl = 15;
expect(questsService.lockQuest(quest,true)).to.not.be.ok;
});
it('does not lock quest if user meets level requirement', function() {
user.stats.lvl = 20;
expect(questsService.lockQuest(quest)).to.not.be.ok;
});
it('locks quest if user has not completed previous quest in series', function() {
quest.previous = 'priorQuest';
user.stats.lvl = 25;
expect(questsService.lockQuest(quest)).to.be.ok;
});
it('does not lock quest if user has completed previous quest in series', function() {
quest.previous = 'priorQuest';
user.stats.lvl = 25;
user.achievements.quests.priorQuest = 1;
expect(questsService.lockQuest(quest)).to.not.be.ok;
});
});
describe('#buyQuest', function() {
var scope;
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
}));
//@TODO: This is fixed in a Quest Service PR port
xit('returns a promise', function() {
var promise = questsService.buyQuest('whale');
expect(promise).to.respondTo('then');
});
context('Quest key does not exist', function() {
it('rejects with message that quest is not found', function(done) {
questsService.buyQuest('foo')
.then(resolveSpy, function(rej) {
expect(rej).to.eql('No quest with that key found');
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
});
context('invite friends', function() {
it('prompts user to invite friends to party for invite reward quests', function() {
questsService.buyQuest('basilist');
expect(window.confirm).to.be.calledOnce;
expect(window.confirm).to.be.calledWith(env.t('mustInviteFriend'));
});
it('rejects promise if confirm is cancelled', function(done) {
window.confirm.returns(false);
questsService.buyQuest('basilist')
.then(resolveSpy, function(rej) {
expect(rej).to.eql('Did not want to invite friends');
expect(window.confirm).to.be.calledOnce;
expect(groupsService.inviteOrStartParty).to.not.be.called;
done();
});
scope.$apply();
});
it('rejects promise if confirm is cofirmed and calls groups service', function(done) {
window.confirm.returns(true);
questsService.buyQuest('basilist')
.then(resolveSpy, function(rej) {
expect(rej).to.eql('Invite or start party');
expect(window.confirm).to.be.calledOnce;
expect(groupsService.inviteOrStartParty).to.be.calledOnce;
done();
});
scope.$apply();
});
});
context('quests in a series', function() {
it('does not allow user to buy subsquent quests in a series if user has no quest achievements', function(done) {
user.stats.lvl = 100;
user.achievements.quests = undefined;
questsService.buyQuest('goldenknight2')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(res).to.eql('unlockByQuesting');
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
it('does not allow user to buy quests whose previous quests are incomplete', function(done) {
user.stats.lvl = 100;
user.achievements.quests = {
'atom1': 1
};
questsService.buyQuest('goldenknight2')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
});
context('quests with level requirement', function() {
it('does not allow user to buy quests beyond their level', function(done) {
user.stats.lvl = 1;
questsService.buyQuest('vice1')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(res).to.eql('mustLvlQuest');
done();
});
scope.$apply();
});
it('allows user to buy quest if they meet level requirement', function(done) {
user.stats.lvl = 30;
questsService.buyQuest('vice1')
.then(function(res) {
expect(res).to.eql(content.quests.vice1);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
context('gold purchasable quests', function() {
it('sends quest object', function(done) {
questsService.buyQuest('dilatoryDistress1')
.then(function(res) {
expect(res).to.eql(content.quests.dilatoryDistress1);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
context('quest bundles', function() {
it('sends bundle object', function(done) {
questsService.buyQuest('featheredFriends')
.then(function(res) {
expect(res).to.eql(content.bundles.featheredFriends);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
context('all other quests', function() {
it('sends quest object', function(done) {
questsService.buyQuest('whale')
.then(function(res) {
expect(res).to.eql(content.quests.whale);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
});
describe('#showQuest', function() {
var scope;
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
}));
xit('returns a promise', function() {
var promise = questsService.showQuest('whale');
expect(promise).to.respondTo('then');
});
context('Quest key does not exist', function() {
it('rejects with message that quest is not found', function(done) {
questsService.showQuest('foo')
.then(resolveSpy, function(rej) {
expect(rej).to.eql('No quest with that key found');
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
});
context('quests in a series', function() {
it('does not allow user to buy subsquent quests in a series if user has no quest achievements', function(done) {
user.stats.lvl = 100;
user.achievements.quests = undefined;
questsService.showQuest('goldenknight2')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(res).to.eql('unlockByQuesting');
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
it('does not allow user to buy quests whose previous quests are incomplete', function(done) {
user.stats.lvl = 100;
user.achievements.quests = {
'atom1': 1
};
questsService.showQuest('goldenknight2')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(resolveSpy).to.not.be.called;
done();
});
scope.$apply();
});
});
context('quests with level requirement', function() {
it('does not allow user to buy quests beyond their level', function(done) {
user.stats.lvl = 1;
questsService.showQuest('vice1')
.then(resolveSpy, function(res) {
expect(window.alert).to.have.been.calledOnce;
expect(res).to.eql('mustLvlQuest');
done();
});
scope.$apply();
});
it('allows user to buy quest if they meet level requirement', function(done) {
user.stats.lvl = 30;
questsService.showQuest('vice1')
.then(function(res) {
expect(res).to.eql(content.quests.vice1);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
context('gold purchasable quests', function() {
it('sends quest object', function(done) {
questsService.showQuest('dilatoryDistress1')
.then(function(res) {
expect(res).to.eql(content.quests.dilatoryDistress1);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
context('all other quests', function() {
it('sends quest object', function(done) {
questsService.showQuest('whale')
.then(function(res) {
expect(res).to.eql(content.quests.whale);
expect(window.alert).to.not.be.called;
expect(rejectSpy).to.not.be.called;
done();
}, rejectSpy);
scope.$apply();
});
});
});
describe('#initQuest', function() {
var fakeBackend, scope, key = 'whale';
beforeEach(inject(function($httpBackend, $rootScope) {
scope = $rootScope.$new();
fakeBackend = $httpBackend;
var partyResponse = {data:{_id: 'party-id'}};
fakeBackend.when('GET', 'partials/main.html').respond({});
fakeBackend.when('GET', 'partials/main.html').respond({});
fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse);
fakeBackend.when('GET', '/api/v3/groups/party-id/members?includeAllPublicFields=true').respond({});
fakeBackend.when('GET', '/api/v3/groups/party-id/invites').respond({});
fakeBackend.when('GET', '/api/v3/challenges/groups/party-id').respond({});
fakeBackend.when('POST', '/api/v3/groups/party-id/quests/invite/' + key).respond({quest: { key: 'whale' } });
fakeBackend.flush();
}));
it('returns a promise', function() {
var promise = questsService.initQuest(key);
expect(promise).to.respondTo('then');
});
it('starts a quest', function(done) {
fakeBackend.expectPOST( '/api/v3/groups/party-id/quests/invite/' + key);
questsService.initQuest(key)
.then(function(res) {
done();
});
fakeBackend.flush();
scope.$apply();
});
it('brings user to party page');
});
//@TODO: This is fixed in a Quest Service PR port
xdescribe('#sendAction', function() {
var fakeBackend, scope;
beforeEach(inject(function($httpBackend, $rootScope) {
scope = $rootScope.$new();
fakeBackend = $httpBackend;
var partyResponse = {data:{_id: 'party-id'}};
fakeBackend.when('GET', 'partials/main.html').respond({});
fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse);
fakeBackend.when('POST', '/api/v3/groups/party-id/quests/reject').respond({quest: { key: 'whale' } });
fakeBackend.flush();
}));
it('returns a promise', function() {
var promise = questsService.sendAction('quests/reject');
expect(promise).to.respondTo('then');
});
it('calls specified quest endpoint', function(done) {
fakeBackend.expectPOST('/api/v3/groups/party-id/quests/reject');
questsService.sendAction('quests/reject')
.then(function(res) {
expect(res.key).to.eql('whale');
done();
});
fakeBackend.flush();
scope.$apply();
});
it('syncs User', function() {
questsService.sendAction('quests/reject')
.then(function(res) {
expect(User.sync).to.be.calledOnce;
done();
});
scope.$apply();
});
});
});
@@ -1,258 +0,0 @@
'use strict';
describe('Stats Service', function() {
var scope, statCalc, user;
beforeEach(function() {
user = specHelper.newUser();
module(function($provide) {
$provide.value('User', {user: user});
});
inject(function($rootScope, $controller, Stats) {
statCalc = Stats;
});
});
describe('beastMasterProgress', function() {
it('counts drop pets that user has', function() {
user.items.pets = {
"BearCub-Base" : 5,
"BearCub-CottonCandyBlue" : 5,
"Cactus-Zombie" : 5,
"Deer-Golden" : 5,
"Deer-Red" : 5,
"Egg-Desert" : 5,
"MantisShrimp-Base" : 5,
"Wolf-Spooky": 5
}
var beastMasterDisplay = statCalc.beastMasterProgress(user.items.pets);
expect(beastMasterDisplay).to.eql('3/90');
});
it('counts drop pets with a value of -1', function() {
user.items.pets = {
"BearCub-Base" : -1,
"BearCub-CottonCandyBlue" : -1,
"Cactus-Zombie" : 5,
"Deer-Golden" : 5,
"Deer-Red" : -1,
"Egg-Desert" : 5,
"MantisShrimp-Base" : 5,
"Wolf-Spooky": -1
}
var beastMasterDisplay = statCalc.beastMasterProgress(user.items.pets);
expect(beastMasterDisplay).to.eql('3/90');
});
it('does not count drop pets with a value of 0', function() {
user.items.pets = {
"BearCub-Base" : 0,
"BearCub-CottonCandyBlue" : 0,
"Cactus-Zombie" : 5,
"Deer-Golden" : 5,
"Deer-Red" : 5,
"Egg-Desert" : 5,
"MantisShrimp-Base" : 5
}
var beastMasterDisplay = statCalc.beastMasterProgress(user.items.pets);
expect(beastMasterDisplay).to.eql('1/90');
});
});
describe('expDisplay', function() {
it('displays exp as "exp / toNextLevelExp"', function() {
user.stats.exp = 10;
user.stats.lvl = 29;
var expDisplay = statCalc.expDisplay(user);
expect(expDisplay).to.eql('10/640');
});
it('Rounds exp down when given a decimal', function() {
user.stats.exp = 10.999;
user.stats.lvl = 29;
var expDisplay = statCalc.expDisplay(user);
expect(expDisplay).to.eql('10/640');
});
});
describe('goldDisplay', function() {
it('displays gold', function() {
var gold = 30;
var goldDisplay = statCalc.goldDisplay(gold);
expect(goldDisplay).to.eql(30);
});
it('Rounds gold down when given a decimal', function() {
var gold = 30.999;
var goldDisplay = statCalc.goldDisplay(gold);
expect(goldDisplay).to.eql(30);
});
});
describe('hpDisplay', function() {
it('displays hp as "hp / totalHP"', function() {
var hp = 34;
var hpDisplay = statCalc.hpDisplay(hp);
expect(hpDisplay).to.eql('34/50');
});
it('Rounds hp up when given a decimal', function() {
var hp = 34.4;
var hpDisplay = statCalc.hpDisplay(hp);
expect(hpDisplay).to.eql('35/50');
});
});
describe('mountMasterProgress', function() {
it('counts drop mounts that user has', function() {
user.items.mounts = {
"Hedgehog-Desert" : true,
"Octopus-CottonCandyPink" : true,
"TigerCub-White" : true,
"Wolf-Golden" : true,
"Owl-CottonCandyBlue" : true,
"Mammoth-Base" : true,
"Bunny-Skeleton" : true,
"Tiger-Spooky": true
}
var mountMasterDisplay = statCalc.mountMasterProgress(user.items.mounts);
expect(mountMasterDisplay).to.eql('2/90');
});
it('does not count drop mounts with a value of false', function() {
user.items.mounts = {
"Hedgehog-Desert" : true,
"Octopus-CottonCandyPink" : true,
"TigerCub-White" : false,
"Wolf-Golden" : false,
"Owl-CottonCandyBlue" : true,
"Mammoth-Base" : true,
"Bunny-Skeleton" : true,
"Tiger-Spooky": true
}
var mountMasterDisplay = statCalc.mountMasterProgress(user.items.mounts);
expect(mountMasterDisplay).to.eql('0/90');
});
});
describe('mpDisplay', function() {
it('displays mp as "mp / totalMP"', function() {
user.fns = {};
user.fns.statsComputed = function () { return { maxMP: 100 } };
user.stats.mp = 30;
var mpDisplay = statCalc.mpDisplay(user);
expect(mpDisplay).to.eql('30/100');
});
it('Rounds mp down when given a decimal', function() {
user.fns = {};
user.fns.statsComputed = function () { return { maxMP: 100 } };
user.stats.mp = 30.99;
var mpDisplay = statCalc.mpDisplay(user);
expect(mpDisplay).to.eql('30/100');
});
});
describe('totalCount', function() {
it('counts all pets that user has', function() {
user.items.pets = {
"BearCub-Base" : 5,
"BearCub-CottonCandyBlue" : 5,
"Cactus-Zombie" : 5,
"Deer-Golden" : 5,
"Deer-Red" : 5,
"Egg-Desert" : 5,
"MantisShrimp-Base" : 5
}
var petsFound = statCalc.totalCount(user.items.pets);
expect(petsFound).to.eql(7);
});
it('includes pets that have a value of 0', function() {
user.items.pets = {
"BearCub-Base" : 0,
"BearCub-CottonCandyBlue" : 5,
"Cactus-Zombie" : 0,
"Deer-Golden" : 0,
"Deer-Red" : 0,
"Egg-Desert" : 0,
"MantisShrimp-Base" : 5
}
var petsFound = statCalc.totalCount(user.items.pets);
expect(petsFound).to.eql(7);
});
it('includes pets that have a value of -1', function() {
user.items.pets = {
"BearCub-Base" : -1,
"BearCub-CottonCandyBlue" : 5,
"Cactus-Zombie" : -1,
"Deer-Golden" : -1,
"Deer-Red" : -1,
"Egg-Desert" : -1,
"MantisShrimp-Base" : 5
}
var petsFound = statCalc.totalCount(user.items.pets);
expect(petsFound).to.eql(7);
});
it('counts all mounts that user has', function() {
user.items.mounts = {
"Hedgehog-Desert" : true,
"Octopus-CottonCandyPink" : true,
"TigerCub-White" : true,
"Wolf-Golden" : true,
"Owl-CottonCandyBlue" : true,
"Mammoth-Base" : true,
"Bunny-Skeleton" : true
}
var mountsFound = statCalc.totalCount(user.items.mounts);
expect(mountsFound).to.eql(7);
});
it('inlcudes mounts with a value of false', function() {
user.items.mounts = {
"Hedgehog-Desert" : false,
"Octopus-CottonCandyPink" : true,
"TigerCub-White" : false,
"Wolf-Golden" : false,
"Owl-CottonCandyBlue" : false,
"Mammoth-Base" : true,
"Bunny-Skeleton" : false
}
var mountsFound = statCalc.totalCount(user.items.mounts);
expect(mountsFound).to.eql(7);
});
});
});
@@ -1,52 +0,0 @@
'use strict';
describe('Tags Service', function() {
var rootScope, tags, user, $httpBackend;
var apiV3Prefix = 'api/v3/tags';
beforeEach(function() {
module(function($provide) {
user = specHelper.newUser();
$provide.value('User', {user: user});
});
inject(function(_$httpBackend_, _$rootScope_, Tags, User) {
$httpBackend = _$httpBackend_;
rootScope = _$rootScope_;
tags = Tags;
});
});
it('calls get tags endpoint', function() {
$httpBackend.expectGET(apiV3Prefix).respond({});
tags.getTags();
$httpBackend.flush();
});
it('calls post tags endpoint', function() {
$httpBackend.expectPOST(apiV3Prefix).respond({});
tags.createTag();
$httpBackend.flush();
});
it('calls get tag endpoint', function() {
var tagId = 1;
$httpBackend.expectGET(apiV3Prefix + '/' + tagId).respond({});
tags.getTag(tagId);
$httpBackend.flush();
});
it('calls update tag endpoint', function() {
var tagId = 1;
$httpBackend.expectPUT(apiV3Prefix + '/' + tagId).respond({});
tags.updateTag(tagId, {});
$httpBackend.flush();
});
it('calls delete tag endpoint', function() {
var tagId = 1;
$httpBackend.expectDELETE(apiV3Prefix + '/' + tagId).respond({});
tags.deleteTag(tagId);
$httpBackend.flush();
});
});
@@ -1,440 +0,0 @@
'use strict';
describe('Tasks Service', function() {
var rootScope, tasks, user, $httpBackend;
var apiV3Prefix = '/api/v3/tasks';
beforeEach(function() {
module(function($provide) {
user = specHelper.newUser();
$provide.value('User', {user: user});
});
inject(function(_$httpBackend_, _$rootScope_, Tasks, User) {
$httpBackend = _$httpBackend_;
rootScope = _$rootScope_;
rootScope.charts = {};
tasks = Tasks;
});
rootScope.openModal = function() {
return {
result: {
then: function() {},
catch: function() {},
},
};
};
});
it('calls get user tasks endpoint', function() {
$httpBackend.expectGET(apiV3Prefix + '/user').respond({});
tasks.getUserTasks();
$httpBackend.flush();
});
it('calls post user tasks endpoint', function() {
$httpBackend.expectPOST(apiV3Prefix + '/user').respond({});
tasks.createUserTasks();
$httpBackend.flush();
});
it('calls get challenge tasks endpoint', function() {
var challengeId = 1;
$httpBackend.expectGET(apiV3Prefix + '/challenge/' + challengeId).respond({});
tasks.getChallengeTasks(challengeId);
$httpBackend.flush();
});
it('calls create challenge tasks endpoint', function() {
var challengeId = 1;
$httpBackend.expectPOST(apiV3Prefix + '/challenge/' + challengeId).respond({});
tasks.createChallengeTasks(challengeId, {});
$httpBackend.flush();
});
it('calls get task endpoint', function() {
var taskId = 1;
$httpBackend.expectGET(apiV3Prefix + '/' + taskId).respond({});
tasks.getTask(taskId);
$httpBackend.flush();
});
it('calls update task endpoint', function() {
var taskId = 1;
$httpBackend.expectPUT(apiV3Prefix + '/' + taskId).respond({});
tasks.updateTask(taskId, {});
$httpBackend.flush();
});
it('calls delete task endpoint', function() {
var taskId = 1;
$httpBackend.expectDELETE(apiV3Prefix + '/' + taskId).respond({});
tasks.deleteTask(taskId);
$httpBackend.flush();
});
it('calls score task endpoint', function() {
var taskId = 1;
var direction = "down";
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/score/' + direction).respond({});
tasks.scoreTask(taskId, direction);
$httpBackend.flush();
});
it('calls move task endpoint', function() {
var taskId = 1;
var position = 0;
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/move/to/' + position).respond({});
tasks.moveTask(taskId, position);
$httpBackend.flush();
});
it('calls group move task endpoint', function() {
var taskId = 1;
var position = 0;
$httpBackend.expectPOST('/api/v3/group-tasks/' + taskId + '/move/to/' + position).respond({});
tasks.moveGroupTask(taskId, position);
$httpBackend.flush();
});
it('calls add check list item endpoint', function() {
var taskId = 1;
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist').respond({});
tasks.addChecklistItem(taskId, {});
$httpBackend.flush();
});
it('calls score check list item endpoint', function() {
var taskId = 1;
var itemId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist/' + itemId + '/score').respond({});
tasks.scoreCheckListItem(taskId, itemId);
$httpBackend.flush();
});
it('calls update check list item endpoint', function() {
var taskId = 1;
var itemId = 2;
$httpBackend.expectPUT(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({});
tasks.updateChecklistItem(taskId, itemId, {});
$httpBackend.flush();
});
it('calls remove check list item endpoint', function() {
var taskId = 1;
var itemId = 2;
$httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({});
tasks.removeChecklistItem(taskId, itemId);
$httpBackend.flush();
});
it('calls add tag to list item endpoint', function() {
var taskId = 1;
var tagId = 2;
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({});
tasks.addTagToTask(taskId, tagId);
$httpBackend.flush();
});
it('calls remove tag to list item endpoint', function() {
var taskId = 1;
var tagId = 2;
$httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({});
tasks.removeTagFromTask(taskId, tagId);
$httpBackend.flush();
});
it('calls unlinkOneTask endpoint', function() {
var taskId = 1;
var keep = "keep";
$httpBackend.expectPOST(apiV3Prefix + '/unlink-one/' + taskId + '?keep=' + keep).respond({});
tasks.unlinkOneTask(taskId);
$httpBackend.flush();
});
it('calls unlinkAllTasks endpoint', function() {
var challengeId = 1;
var keep = "keep-all";
$httpBackend.expectPOST(apiV3Prefix + '/unlink-all/' + challengeId + '?keep=' + keep).respond({});
tasks.unlinkAllTasks(challengeId);
$httpBackend.flush();
});
it('calls clear completed todo task endpoint', function() {
$httpBackend.expectPOST(apiV3Prefix + '/clearCompletedTodos').respond({});
tasks.clearCompletedTodos();
$httpBackend.flush();
});
describe('editTask', function() {
var task;
beforeEach(function(){
task = specHelper.newTask();
});
it('sets _editing to true', function() {
tasks.editTask(task, user);
expect(task._editing).to.eql(true);
});
it('sets _tags to true by default', function() {
tasks.editTask(task, user);
expect(task._tags).to.eql(true);
});
it('sets _tags to false if preference for collapsed tags is turned on', function() {
user.preferences.tagsCollapsed = true;
tasks.editTask(task, user);
expect(task._tags).to.eql(false);
});
it('sets _advanced to true by default', function(){
user.preferences.advancedCollapsed = true;
tasks.editTask(task, user);
expect(task._advanced).to.eql(false);
});
it('sets _advanced to false if preference for collapsed advance menu is turned on', function() {
user.preferences.advancedCollapsed = false;
tasks.editTask(task, user);
expect(task._advanced).to.eql(true);
});
it('closes task chart if it exists', function() {
rootScope.charts[task.id] = true;
tasks.editTask(task, user);
expect(rootScope.charts[task.id]).to.eql(false);
});
});
describe('cancelTaskEdit', function() {
var task;
beforeEach(function(){
task = specHelper.newTask();
});
it('sets _editing to false', function() {
task._editing = true;
tasks.cancelTaskEdit(task);
expect(task._editing).to.eql(false);
});
});
describe('cloneTask', function() {
context('generic tasks', function() {
it('clones the data from a task', function() {
var task = specHelper.newTask();
var clonedTask = tasks.cloneTask(task);
expect(clonedTask.text).to.eql(task.text);
expect(clonedTask.notes).to.eql(task.notes);
expect(clonedTask.tags.includedTag).to.eql(task.tags.includedTag);
expect(clonedTask.tags.notIncludedTag).to.eql(task.tags.notIncludedTag);
expect(clonedTask.priority).to.eql(task.priority);
expect(clonedTask.attribute).to.eql(task.attribute);
});
it('does not clone original task\'s _id', function() {
var task = specHelper.newTask();
var clonedTask = tasks.cloneTask(task);
expect(clonedTask._id).to.exist;
expect(clonedTask._id).to.not.eql(task._id);
});
it('does not clone original task\'s dateCreated attribute', function() {
var task = specHelper.newTask({
createdAt: new Date(2014, 5, 1, 1, 1, 1, 1),
});
var clonedTask = tasks.cloneTask(task);
expect(clonedTask.createdAt).to.exist;
expect(clonedTask.createdAt).to.not.eql(task.createdAt);
});
it('does not clone original task\'s value', function() {
var task = specHelper.newTask({
value: 130
});
var clonedTask = tasks.cloneTask(task);
expect(clonedTask.value).to.exist;
expect(clonedTask.value).to.not.eql(task.value);
});
});
context('Habits', function() {
it('clones a habit', function() {
var habit = specHelper.newHabit({
up: true,
down: false
});
var clonedHabit = tasks.cloneTask(habit);
expect(clonedHabit.type).to.eql('habit');
expect(clonedHabit.up).to.eql(habit.up);
expect(clonedHabit.down).to.eql(habit.down);
});
});
context('Dailys', function() {
it('clones a daily', function() {
var daily = specHelper.newDaily({
frequency: 'daily',
everyX: 3,
startDate: new Date(2014, 5, 1, 1, 1, 1, 1),
});
var clonedDaily = tasks.cloneTask(daily);
expect(clonedDaily.type).to.eql('daily');
expect(clonedDaily.frequency).to.eql(daily.frequency);
expect(clonedDaily.everyX).to.eql(daily.everyX);
expect(clonedDaily.startDate).to.eql(daily.startDate);
});
it('does not clone streak', function() {
var daily = specHelper.newDaily({
streak: 11
});
var clonedDaily = tasks.cloneTask(daily);
expect(clonedDaily.streak).to.eql(0);
});
});
context('Todos', function() {
it('clones a todo', function() {
var todo = specHelper.newTodo();
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.type).to.eql('todo');
});
it('does not clone due date', function() {
var todo = specHelper.newTodo({
date: '2015-06-20'
});
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.date).to.not.exist;
});
it('does not clone date completed', function() {
var todo = specHelper.newTodo({
dateCompleted: new Date()
});
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.dateCompleted).to.not.exist;
});
});
context('Rewards', function() {
it('clones a reward', function() {
var reward = specHelper.newReward();
var clonedReward = tasks.cloneTask(reward);
expect(clonedReward.type).to.eql('reward');
});
it('does clone a reward\'s vaue', function() {
var reward = specHelper.newReward({
value: 20
});
var clonedReward = tasks.cloneTask(reward);
expect(clonedReward.value).to.eql(reward.value);
});
});
context('complete', function() {
it('does not clone completed status', function() {
var todo = specHelper.newTodo({
completed: true
});
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.completed).to.eql(false);
});
});
context('history', function() {
it('does not clone history', function() {
var habit = specHelper.newHabit({
history: [
{ date: Date.now, value: 3.1 },
{ date: Date.now, value: 2.7 }
]
});
var clonedHabit = tasks.cloneTask(habit);
expect(clonedHabit.history).to.be.an.array;
expect(clonedHabit.history).to.be.empty;
});
});
context('checklists', function() {
it('clones checklist text', function() {
var todo = specHelper.newTodo({
checklist: [{
completed: true,
text: 'checklist 1',
id: 'cl-1'
}, {
completed: false,
text: 'checklist 2',
id: 'cl-2'
}]
});
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.checklist[0].text).to.eql(todo.checklist[0].text);
expect(clonedTodo.checklist[1].text).to.eql(todo.checklist[1].text);
});
it('does not clone complete or id attribute of checklist', function() {
var todo = specHelper.newTodo({
checklist: [{
completed: true,
text: 'checklist 1',
id: 'cl-1'
}, {
completed: false,
text: 'checklist 2',
id: 'cl-2'
}]
});
var clonedTodo = tasks.cloneTask(todo);
expect(clonedTodo.checklist[0].completed).to.eql(false);
expect(clonedTodo.checklist[0].id).to.not.eql(todo.checklist[0].id);
expect(clonedTodo.checklist[0].id).to.exist;
expect(clonedTodo.checklist[1].completed).to.eql(false);
expect(clonedTodo.checklist[1].id).to.not.eql(todo.checklist[1].id);
expect(clonedTodo.checklist[1].id).to.exist;
});
});
});
});
@@ -1,64 +0,0 @@
'use strict';
describe('userServices', function() {
var $httpBackend, $window, user, STORAGE_USER_ID, STORAGE_SETTINGS_ID;
beforeEach(module('habitrpg'));
beforeEach(function(){
module(function($provide){
$window = {href: '', alert: sandbox.spy(), location: {search: '', pathname: '', href: ''}};
$provide.value('$window', $window);
});
inject(function(_$httpBackend_, User, _STORAGE_USER_ID_, _STORAGE_SETTINGS_ID_) {
$httpBackend = _$httpBackend_;
user = User;
STORAGE_USER_ID = _STORAGE_USER_ID_;
STORAGE_SETTINGS_ID = _STORAGE_SETTINGS_ID_;
});
localStorage.removeItem(STORAGE_SETTINGS_ID);
localStorage.removeItem(STORAGE_USER_ID);
});
it('checks online status', function(){
user.online(true);
expect(user.settings.online).to.be.true;
user.online(false);
expect(user.settings.online).to.be.false;
})
it('saves user data to local storage', function(){
user.save();
var settings = JSON.parse(localStorage[STORAGE_SETTINGS_ID]);
expect(settings).to.eql(user.settings);
});
xit('alerts when not authenticated', function(){
user.log();
expect($window.alert).to.have.been.calledWith("Not authenticated, can't sync, go to settings first.");
});
xit('puts items in que queue', function(){
user.log({});
//TODO where does that null comes from?
expect(user.settings.sync.queue).to.eql([null, {}]);
});
describe('getBalanceInGems', function() {
it('multiplies balance by 4', function() {
user.user.balance = 5;
var balanceInGems = user.getBalanceInGems();
expect(balanceInGems).to.eql(20);
});
it('returns zero if balance is not defined', function() {
var balanceInGems = user.getBalanceInGems();
expect(user.user.balance).to.not.exist;
expect(balanceInGems).to.eql(0);
});
});
});

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