Compare commits

..

157 Commits

Author SHA1 Message Date
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
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
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
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
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
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
thehollidayinn
b0dcc2f6ef 4.1.4 2017-10-04 18:35:50 -05:00
SabreCat
3377f8a916 Merge branch 'develop' into release 2017-10-04 23:30:39 +00:00
Keith Holliday
22f83d09c4 Release with develop (#9162)
* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)

* 4.1.3

* use `selectGearToPin` instead of updateStore in migration-script (#9102)

* Tags redesign in edit-task modal (#9122)

* Truncate tags list to maximum number of tasks

This commit truncates the list of tags in the edit task modal and
displays the remaining selected tasks as a number.

* Align tags-select dropdown with "Tags" label

* Add tags popup component

* Use solid purple for tags-select dropdown

* Remove shadow when tags-select is active
* Add border-radius to tags-select
* Re-add previously disabled transitions
* Remove unused template element

* Add Clear Tags button to footer of tags popup

* Decrease column size for tags to better match design
* Truncate tag name to avoid overflows
* Add tag name as title to show full name on hover

* Grow inline tags select from left to right

* Style none button
* Add spacing to streak reset button to line up with tags select

* Add top offset to tags dropdown toggle to line up with label

* Ability to collapse checklists (#9158)

* ability to collapse checklists

* typo

* show warning to use the mobile apps (#9152)

* Oct 4 fixes (#9159)

* Added gem purchase note

* Added notification count

* Added party reload

* Added description when user can no longer purchase gems this month

* Prevent modal from showing when pruchasing recently purchased items

* Added progress bar

* Prevented non leader from loading approvals

* Added group billing

* Release fix (#9161)

* Merge Develop onto Release (#9123)

* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)

* various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)

* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down

* Client Fixes (#9120)

* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications

* Oct 1 fixes (#9121)

* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health

* Added missing notification file

* 4.1.1

* 4.1.2

* Merge develop into release (#9154)

* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-04 18:26:05 -05:00
Keith Holliday
496534ab4b Release fix (#9161)
* Merge Develop onto Release (#9123)

* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)

* various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)

* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down

* Client Fixes (#9120)

* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications

* Oct 1 fixes (#9121)

* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health

* Added missing notification file

* 4.1.1

* 4.1.2

* Merge develop into release (#9154)

* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-04 17:39:34 -05:00
Keith Holliday
7325d2020e Oct 4 fixes (#9159)
* Added gem purchase note

* Added notification count

* Added party reload

* Added description when user can no longer purchase gems this month

* Prevent modal from showing when pruchasing recently purchased items

* Added progress bar

* Prevented non leader from loading approvals

* Added group billing
2017-10-04 16:47:19 -05:00
negue
1a0d39e566 show warning to use the mobile apps (#9152) 2017-10-04 21:19:10 +02:00
Matteo Pagliazzi
dc62ab7577 Ability to collapse checklists (#9158)
* ability to collapse checklists

* typo
2017-10-04 20:35:59 +02:00
Michael Hibbs
c4e5633e48 Tags redesign in edit-task modal (#9122)
* Truncate tags list to maximum number of tasks

This commit truncates the list of tags in the edit task modal and
displays the remaining selected tasks as a number.

* Align tags-select dropdown with "Tags" label

* Add tags popup component

* Use solid purple for tags-select dropdown

* Remove shadow when tags-select is active
* Add border-radius to tags-select
* Re-add previously disabled transitions
* Remove unused template element

* Add Clear Tags button to footer of tags popup

* Decrease column size for tags to better match design
* Truncate tag name to avoid overflows
* Add tag name as title to show full name on hover

* Grow inline tags select from left to right

* Style none button
* Add spacing to streak reset button to line up with tags select

* Add top offset to tags dropdown toggle to line up with label
2017-10-04 12:33:35 -05:00
negue
d977656e96 use selectGearToPin instead of updateStore in migration-script (#9102) 2017-10-04 10:59:44 -05:00
Keith Holliday
b0a980d56e Merge develop into release (#9154)
* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-03 21:15:00 -05:00
Alys
3d75c99f8d fix some text describing menu items (#9145) 2017-10-04 09:24:44 +10:00
Keith Holliday
0aa16d7021 Oct 3 fixes (#9148)
* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting
2017-10-03 16:33:01 -05:00
negue
4fa3046104 sep 27 fixes (#9088)
* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height
2017-10-03 21:44:45 +02:00
Matteo Pagliazzi
fda4be01b8 limit for inlined svg images and make home leaner by not bundling it with the rest of static pages 2017-10-03 17:31:53 +02:00
Brian Murray
67436fbef1 Be consistent with capitalization of Check-In. (#9118) 2017-10-04 00:25:58 +10:00
Alys
ffb92a5faa quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)
* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings
2017-10-04 00:19:49 +10:00
Matteo Pagliazzi
4a44d60fac Client: fix Apidoc and move email files (#9139)
* fix apidoc

* move emails files
2017-10-03 15:34:12 +02:00
Sabe Jones
122cc510d3 4.1.2 2017-10-03 01:44:30 +00:00
Sabe Jones
29a7a07d14 Merge branch 'develop' into release 2017-10-03 01:39:53 +00:00
Sabe Jones
18783aefe3 chore(i18n): update locales 2017-10-03 01:38:52 +00:00
Sabe Jones
a5e242759c chore(news): Bailey 20171002 (#9131)
Extend subscriber item and Farm Friends duration
Parse Markdown in achievement challenges list
2017-10-02 20:32:13 -05:00
Keith Holliday
e01c6cc9a6 Added buy special support to buy route (#9130) 2017-10-02 20:31:06 -05:00
Keith Holliday
8a75383c43 Added spooky sparkles to features (#9129) 2017-10-02 19:46:21 -05:00
Matteo Pagliazzi
47ebee9ae8 Client Fixes Oct 1 (#9126)
* fix exp notifications for armoire

* open the profile modal only if data is available
2017-10-02 23:01:27 +02:00
Keith Holliday
4e97355110 Oct 2 fixes (#9124)
* Prevented filters from closing when editing tags

* Fixed group plan upgrade

* Add check for class change

* Fixed scrolling stopping on bailey

* Added server side results for armoire

* Added persistend equipment drawer toggle

* Updated reactivity in amazon payments
2017-10-02 12:51:17 -05:00
Matteo Pagliazzi
7045b5c214 update notifications lib 2017-10-02 09:26:41 +02:00
Sabe Jones
e41dccf6d5 4.1.1 2017-10-02 02:01:31 +00:00
Sabe Jones
d0815ea9ee Merge branch 'develop' into release 2017-10-02 01:51:08 +00:00
Sabe Jones
d0bd62bf02 chore(i18n): update locales 2017-10-02 01:49:12 +00:00
Keith Holliday
39d7581c6c Merge Develop onto Release (#9123)
* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)

* various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)

* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down

* Client Fixes (#9120)

* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications

* Oct 1 fixes (#9121)

* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health

* Added missing notification file
2017-10-01 20:42:02 -05:00
Keith Holliday
fdf146dd01 Merge branch 'release' into develop 2017-10-01 19:46:57 -05:00
thehollidayinn
023b3ffd88 Added missing notification file 2017-10-01 19:45:21 -05:00
Keith Holliday
f01e552637 Oct 1 fixes (#9121)
* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health
2017-10-01 19:17:02 -05:00
Matteo Pagliazzi
9f11820a02 Client Fixes (#9120)
* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications
2017-10-01 23:25:32 +02:00
Alys
ca6c7b8e5f various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)
* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down
2017-10-01 22:15:44 +10:00
Keith Holliday
02f8ba1638 4.1.0 (#9116) 2017-09-30 23:59:52 -05:00
Keith Holliday
8eb7c67f12 Merge develop to release (#9115)
* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)
2017-09-30 23:54:12 -05:00
Keith Holliday
1f895fda44 Reset local creds if access is denied (#9114) 2017-09-30 23:15:24 -05:00
Keith Holliday
e87c180e9b Skill fixes (#9113)
* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo
2017-09-30 23:04:18 -05:00
Keith Holliday
dbf9fd54be Tasks tags (#9112)
* Added auto apply and exit

* Add challenge tag editing

* Fixed lint
2017-09-30 21:49:00 -05:00
Keith Holliday
c9b99d1ecf Some random quick (#9111)
* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning
2017-09-30 17:54:27 -05:00
Sabe Jones
fd8120c80d 4.0.9 2017-09-30 17:38:49 +00:00
Sabe Jones
053e75562f Merge branch 'develop' into release 2017-09-30 17:38:34 +00:00
Sabe Jones
1d50027f51 fix(challenges): Markdown titles 2017-09-30 17:10:57 +00:00
Sabe Jones
7fe74fd06a Serve static news page for mobile (#9109)
* fix(news): serve static page for mobile

* fix(news): commit new component

* refactor(news): computed prop DRY
2017-09-30 10:48:24 -05:00
Sabe Jones
173a8561b6 4.0.8 2017-09-30 13:37:29 +00:00
Sabe Jones
f21bef707b Merge branch 'develop' into release 2017-09-30 13:36:40 +00:00
Keith Holliday
9cf2ccf7c4 Style fixes (#9108)
* fixed some columns on smaller screens

* changed mystic hourglass check

* Checked for bad data

* Groupped pets and mounts

* Added show more

* Fixed lint

* Fixed lint
2017-09-30 08:34:25 -05:00
Keith Holliday
77d75c4669 Pending quest styles (#9106)
* Added pending quest modal

* Added quest completed modal

* Removed duplicate string
2017-09-30 08:28:39 -05:00
thehollidayinn
1c17b95146 4.0.7 2017-09-29 20:18:00 -05:00
Trevor Ford
d89fc209d1 fix allocate() function in (#9105)
website/client/components/userMenu/profile.vue
2017-09-29 18:48:24 -05:00
Sabe Jones
844c8bb3e6 4.0.6 2017-09-29 22:08:23 +00:00
Sabe Jones
569fb11db8 Merge branch 'develop' into release 2017-09-29 22:08:11 +00:00
Keith Holliday
7671347d3a Chat avatar fixes (#9103)
* Ensured rejection doesn't hurt performance

* Added debounce and scroll removal

* Added debounce for keydown

* Fixed linting
2017-09-29 16:03:43 -05:00
Alys
dc3a02bc2e clarify change class code and remove incorrect confirmation step (#9099)
- prevent the user seeing a confirmation about paying 3 gems when enabling the class system (which is free)
- rename changeClass function to changeClassForUser to avoid confusion with changeClass function
- make change class confirmation translatable
- changed "gems" to "Gems" in locales string
2017-09-29 15:57:05 -05:00
Alys
1d8c126687 hide the Block button in the user's own profile screen (#9098)
This prevents a user from accidentally blocking themself from sending PMs.
2017-09-29 15:56:54 -05:00
Alys
7ee49a43f2 Various fixes: mods can delete any message; no Report / CopyAsTodo button in inbox; etc (#9091)
* fix link to Data Display Tool

* prevent the Copy As To-Do button appearing in the inbox because it isn't working

* allow mods/staff to delete any chat message

* prevent the Flag/Report button appearing in the inbox because it isn't working
2017-09-29 15:56:44 -05:00
Sabe Jones
900bc8dfc1 Merge branch 'develop' into release 2017-09-29 20:34:52 +00:00
Keith Holliday
ec260016d3 Task sort payment fixes (#9104)
* Added sorting to dated filter

* Validated payment data type
2017-09-29 15:30:28 -05:00
Sabe Jones
ec770fb29e 4.0.5 2017-09-29 16:19:02 +00:00
Matteo Pagliazzi
c1079e4eae Client Tasks Length (#9101)
* change tasks column height

* stack columns on smaller screens
2017-09-29 18:17:58 +02:00
Sabe Jones
3cb5637fd5 Merge branch 'release' into develop 2017-09-29 15:21:10 +00:00
Matteo Pagliazzi
cf3a0118c9 fix typo 2017-09-29 12:39:18 +02:00
Matteo Pagliazzi
895a383089 cache what is cacheable (because hashed) in the new client, should fix the broken navigation while using the site before and after a deploy 2017-09-29 12:37:03 +02:00
Matteo Pagliazzi
f730e7b345 fix password reset (setting a new one) 2017-09-29 12:15:31 +02:00
Alys
d8f3d99d59 fix footer link to Report a Bug guild 2017-09-29 18:57:33 +10:00
Sabe Jones
99fb1f6116 4.0.4 2017-09-29 01:56:18 +00:00
Alys
7c6dce2124 change link in Bailey from G docs to wiki 2017-09-29 11:51:50 +10:00
Sabe Jones
c757a3f52d 4.0.3 2017-09-28 23:50:44 +00:00
Sabe Jones
ffb318fe8d fix(news): sprite path 2017-09-28 23:50:29 +00:00
Sabe Jones
19cd15ed62 4.0.2 2017-09-28 23:37:16 +00:00
Sabe Jones
3495662196 fix(redesign): Bailey and debug 2017-09-28 23:37:00 +00:00
SabreCat
908a1340a4 4.0.1 2017-09-28 22:59:07 +00:00
Keith Holliday
31f4610b20 Sabrecat/redesign launch bailey (#9094)
* chore(news): Bailey

* chore(news): Bailey on old client

* Fixed seasonal images

* Fixed footer and images for bailey
2017-09-28 15:36:48 -05:00
8009 changed files with 38240 additions and 123899 deletions

View File

@@ -1,3 +0,0 @@
{
"directory": "website/client-old/bower_components"
}

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"

3
.gitignore vendored
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

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

View File

@@ -4,14 +4,13 @@ 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
WORKDIR /usr/src/habitrpg
RUN git clone --branch release https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.0.3 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

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');
};

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"
}
}

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);

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']);
});

View File

@@ -2,13 +2,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') {
gulp.start('build:prod');
} else {
gulp.start('build:dev');
}
});
@@ -34,23 +31,8 @@ gulp.task('build:client', ['bootstrap'], (done) => {
});
});
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:client',
], (done) => {
runSequence(
'grunt-build:prod',
'apidoc',
done
);
});
'apidoc',
]);

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')()
);
});

View File

@@ -10,25 +10,24 @@ 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']);
gulp.task('sprites:main', () => {
let mainSrc = sync('website/assets/sprites/spritesmith/**/*.png');
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/assets/sprites/spritesmith_large/**/*.png');
let largeSrc = sync('website/raw_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);
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
@@ -36,7 +35,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${DIST_PATH}*.png`);
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img, index) => {
let spriteSize = calculateImgDimensions(img);
@@ -68,18 +67,16 @@ function createSpritesStream (name, src) {
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
cssVarMap: cssVarMap,
}));
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
.pipe(gulp.dest(IMG_DIST_PATH));
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
.pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);

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,

View File

@@ -29,7 +29,6 @@ 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 = [];
@@ -75,25 +74,11 @@ gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
}
});
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) => {
@@ -185,99 +170,6 @@ 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'),
@@ -331,7 +223,6 @@ gulp.task('test', (done) => {
'test:sanity',
'test:content',
'test:common',
'test:karma',
'test:api-v3:unit',
'test:api-v3:integration',
done

View File

@@ -10,9 +10,7 @@ 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');
} else {
require('glob').sync('./gulp/gulp-*').forEach(require);

View File

@@ -1,4 +1,5 @@
var updateStore = require('../website/common/script/libs/updateStore');
import { selectGearToPin } from '../website/common/script/ops/pinnedGearUtils';
var getItemInfo = require('../website/common/script/libs/getItemInfo');
var migrationName = '20170928_redesign_launch.js';
@@ -69,7 +70,7 @@ function updateUser (user) {
var set = {'migration': migrationName};
var oldRewardsList = updateStore(user);
var oldRewardsList = selectGearToPin(user);
var newPinnedItems = [
{
type: 'armoire',

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']
}
}
};

6179
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.0.1",
"version": "4.6.3",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -14,6 +14,7 @@
"autoprefixer": "^6.4.0",
"aws-sdk": "^2.0.25",
"axios": "^0.16.0",
"axios-progress-bar": "^0.1.7",
"babel-core": "^6.0.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.0.0",
@@ -31,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",
@@ -52,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",
@@ -155,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",
@@ -169,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"
},
@@ -193,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",

View File

@@ -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);
});
});

View File

@@ -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 () => {

View File

@@ -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);
});
});
});

View File

@@ -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;
@@ -56,6 +56,7 @@ describe('POST /user/buy/:key', () => {
message: t('messageHealthAlreadyMax'),
});
});
it('buys a piece of gear', async () => {
let key = 'armor_warrior_1';
@@ -64,4 +65,36 @@ describe('POST /user/buy/:key', () => {
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
});
it('buys a special spell', async () => {
let key = 'spookySparkles';
let item = content.special[key];
await user.update({'stats.gp': 250});
let res = await user.post(`/user/buy/${key}`);
await user.sync();
expect(res.data).to.eql({
items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared
stats: user.stats,
});
expect(res.message).to.equal(t('messageBought', {
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()}));
});
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
});
});
});

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,
});
});
});

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);

View File

@@ -1,7 +0,0 @@
{
"globals": {
"browser": true,
"by": true,
"element": true
}
}

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();
}
});

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;
},
};

View File

@@ -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');
});

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;
});
});
});

View File

@@ -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');
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});
});
});

View File

@@ -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');
});
});

View File

@@ -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;
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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');
});
});
});

View File

@@ -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() { });
});

View File

@@ -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);
});
});
});

View File

@@ -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;
});
});
});

View File

@@ -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);
});
});

View File

@@ -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;
});
});
});

View File

@@ -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
}));
});
});
});

View File

@@ -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(['紅色軟帽', '紅色盾牌']);
});
});
});

View File

@@ -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.');
});
});
});
});

View File

@@ -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);
}));
});
});
});

View File

@@ -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'});
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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;
});
});
});
});

View File

@@ -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;
});
});
});
});

View File

@@ -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;
});
});
});

View File

@@ -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});
});
});
});

View File

@@ -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');
});
});
});

View File

@@ -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;
}));
});

View File

@@ -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');
}));
});
});

View File

@@ -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;
});
})

View File

@@ -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");
}));
});

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);
}));
});
});

View File

@@ -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]]);
});
});
});

View File

@@ -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');
}));
});

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,
});
};

View File

@@ -1,5 +0,0 @@
var sandbox = sinon.sandbox.create();
afterEach(function() {
sandbox.restore();
});

View File

@@ -1,8 +0,0 @@
'use strict'
var analyticsMock = {
login: sandbox.spy(),
register: sandbox.spy(),
updateUser: sandbox.spy(),
track: sandbox.spy()
};

View File

@@ -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' }
);
});
});
});

View File

@@ -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);
});
});
});
});
});

View File

@@ -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();
});
});

View File

@@ -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();
});
});

View File

@@ -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'));
});
});

View File

@@ -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);
});
});
});
});

View File

@@ -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;
});
});

View File

@@ -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();
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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();
});
});

View File

@@ -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;
});
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -1,179 +0,0 @@
beforeEach(module('habitrpg'));
var specHelper = {};
(function(){
specHelper.newUser = newUser;
specHelper.newGroup = newGroup;
specHelper.newTask = newTask;
specHelper.newHabit = newHabit;
specHelper.newDaily = newDaily;
specHelper.newTodo = newTodo;
specHelper.newReward = newReward;
specHelper.newChallenge = newChallenge;
function newUser(overrides) {
var buffs = { per:0, int:0, con:0, str:0, stealth: 0, streaks: false };
var stats = { str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs, gp: 0 };
var items = {
lastDrop: { count: 0 },
hatchingPotions: {},
eggs: {},
food: {},
pets: {},
mounts: {},
gear: { equipped: {}, costume: {}, owned: {} }
};
var user = {
_id: 'unique-user-id',
profile: {
name: 'dummy-name',
},
auth: { timestamps: {} },
stats: stats,
items: items,
party: {
quest: {
progress: {down: 0}
}
},
preferences: { suppressModals: {} },
habits: [],
dailys: [],
todos: [],
rewards: [],
flags: {},
filters: {},
achievements: {}
};
_setOverrides(user, overrides);
return user;
}
function newGroup(overrides) {
var quest = { progress: { }, active: false };
var group = {
_id: 'group-id',
leader : 'leader-id',
memberCount : 1,
chat : [],
privacy : "public",
invites : [],
members : [
'leader-id'
]
};
_setOverrides(group, overrides);
return group;
}
function newTask(overrides) {
var task = {
id: 'task-id',
_id: 'task-id',
dateCreated: Date.now,
text: 'task text',
notes: 'task notes',
tags: { },
value: 0,
priority: 1,
attribute: 'str',
challenge: { }
};
_setOverrides(task, overrides);
return task;
}
function newHabit(overrides) {
var habit = newTask();
habit.type = 'habit';
habit.history = [];
habit.up = true;
habit.down = true;
_setOverrides(habit, overrides);
return habit;
}
function newDaily(overrides) {
var daily = newTask();
daily.type = 'daily';
daily.frequency = 'weekly';
daily.repeat = {
m: true,
t: true,
w: true,
th: true,
f: true,
s: true,
su: true
};
daily.startDate = Date.now;
daily.history = [];
daily.completed = false;
daily.collapseChecklist = false;
daily.checklist = [];
daily.streak = 0;
_setOverrides(daily, overrides);
return daily;
}
function newTodo(overrides) {
var todo = newTask();
todo.type = 'todo';
todo.completed = false;
todo.collapseChecklist = false;
todo.checklist = [];
_setOverrides(todo, overrides);
return todo;
}
function newReward(overrides) {
var reward = newTask();
reward.type = 'reward';
_setOverrides(reward, overrides);
return reward;
}
function newChallenge(overrides) {
var challenge = {
name: 'challenge name',
description: 'challeng description',
habits: [],
dailys: [],
todos: [],
rewards: [],
leader: 'leader-id',
group: 'group-id',
prize: 0,
timestamp: +(new Date),
members: ['leader-id'],
official: false
};
_setOverrides(challenge, overrides);
return challenge;
}
function _setOverrides(factory, overrides) {
for(var key in overrides) {
factory[key] = overrides[key];
}
}
})();

View File

@@ -25,6 +25,15 @@ describe('shared.ops.blockUser', () => {
}
});
it('validates user can\'t block himself', (done) => {
try {
blockUser(user, { params: { uuid: user._id } });
} catch (error) {
expect(error.message).to.eql(i18n.t('blockYourself'));
done();
}
});
it('blocks user', () => {
let [result] = blockUser(user, { params: { uuid: blockedUser._id } });
expect(user.inbox.blocks).to.eql([blockedUser._id]);

View File

@@ -1,61 +0,0 @@
/* eslint-disable camelcase */
import {
generateUser,
} from '../../helpers/common.helper';
import buy from '../../../website/common/script/ops/buy';
import {
BadRequest,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
describe('shared.ops.buy', () => {
let user;
beforeEach(() => {
user = generateUser({
items: {
gear: {
owned: {
weapon_warrior_0: true,
},
equipped: {
weapon_warrior_0: true,
},
},
},
stats: { gp: 200 },
});
});
it('returns error when key is not provided', (done) => {
try {
buy(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});
it('recovers 15 hp', () => {
user.stats.hp = 30;
buy(user, {params: {key: 'potion'}});
expect(user.stats.hp).to.eql(45);
});
it('adds equipment to inventory', () => {
user.stats.gp = 31;
buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.owned).to.eql({
weapon_warrior_0: true,
armor_warrior_1: true,
eyewear_special_blackTopFrame: true,
eyewear_special_blueTopFrame: true,
eyewear_special_greenTopFrame: true,
eyewear_special_pinkTopFrame: true,
eyewear_special_redTopFrame: true,
eyewear_special_whiteTopFrame: true,
eyewear_special_yellowTopFrame: true,
});
});
});

124
test/common/ops/buy/buy.js Normal file
View File

@@ -0,0 +1,124 @@
/* eslint-disable camelcase */
import {
generateUser,
} from '../../../helpers/common.helper';
import buy from '../../../../website/common/script/ops/buy';
import {
BadRequest,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import content from '../../../../website/common/script/content/index';
describe('shared.ops.buy', () => {
let user;
beforeEach(() => {
user = generateUser({
items: {
gear: {
owned: {
weapon_warrior_0: true,
},
equipped: {
weapon_warrior_0: true,
},
},
},
stats: { gp: 200 },
});
});
it('returns error when key is not provided', (done) => {
try {
buy(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});
it('recovers 15 hp', () => {
user.stats.hp = 30;
buy(user, {params: {key: 'potion'}});
expect(user.stats.hp).to.eql(45);
});
it('adds equipment to inventory', () => {
user.stats.gp = 31;
buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.owned).to.eql({
weapon_warrior_0: true,
armor_warrior_1: true,
eyewear_special_blackTopFrame: true,
eyewear_special_blueTopFrame: true,
eyewear_special_greenTopFrame: true,
eyewear_special_pinkTopFrame: true,
eyewear_special_redTopFrame: true,
eyewear_special_whiteTopFrame: true,
eyewear_special_yellowTopFrame: true,
});
});
it('buys Steampunk Accessories Set', () => {
user.purchased.plan.consecutive.trinkets = 1;
buy(user, {
params: {
key: '301404',
},
type: 'mystery',
});
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
expect(user.items.gear.owned).to.have.property('weapon_mystery_301404', true);
expect(user.items.gear.owned).to.have.property('armor_mystery_301404', true);
expect(user.items.gear.owned).to.have.property('head_mystery_301404', true);
expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true);
});
it('buys a Quest scroll', () => {
user.stats.gp = 205;
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
});
expect(user.items.quests).to.eql({dilatoryDistress1: 1});
expect(user.stats.gp).to.equal(5);
});
it('buys a special item', () => {
user.stats.gp = 11;
let item = content.special.thankyou;
let [data, message] = buy(user, {
params: {
key: 'thankyou',
},
type: 'special',
});
expect(user.stats.gp).to.equal(1);
expect(user.items.special.thankyou).to.equal(1);
expect(data).to.eql({
items: user.items,
stats: user.stats,
});
expect(message).to.equal(i18n.t('messageBought', {
itemText: item.text(),
}));
});
it('allows for bulk purchases', () => {
user.stats.hp = 30;
buy(user, {params: {key: 'potion'}, quantity: 2});
expect(user.stats.hp).to.eql(50);
});
});

View File

@@ -2,15 +2,15 @@
import {
generateUser,
} from '../../helpers/common.helper';
import count from '../../../website/common/script/count';
import buyArmoire from '../../../website/common/script/ops/buyArmoire';
import randomVal from '../../../website/common/script/libs/randomVal';
import content from '../../../website/common/script/content/index';
} from '../../../helpers/common.helper';
import count from '../../../../website/common/script/count';
import buyArmoire from '../../../../website/common/script/ops/buyArmoire';
import randomVal from '../../../../website/common/script/libs/randomVal';
import content from '../../../../website/common/script/content/index';
import {
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
function getFullArmoire () {
let fullArmoire = {};

View File

@@ -3,13 +3,13 @@
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../helpers/common.helper';
import buyGear from '../../../website/common/script/ops/buyGear';
import shared from '../../../website/common/script';
} from '../../../helpers/common.helper';
import buyGear from '../../../../website/common/script/ops/buyGear';
import shared from '../../../../website/common/script';
import {
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyGear', () => {
let user;

View File

@@ -1,12 +1,12 @@
/* eslint-disable camelcase */
import {
generateUser,
} from '../../helpers/common.helper';
import buyHealthPotion from '../../../website/common/script/ops/buyHealthPotion';
} from '../../../helpers/common.helper';
import buyHealthPotion from '../../../../website/common/script/ops/buyHealthPotion';
import {
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyHealthPotion', () => {
let user;

View File

@@ -2,13 +2,13 @@
import {
generateUser,
} from '../../helpers/common.helper';
import buyMysterySet from '../../../website/common/script/ops/buyMysterySet';
} from '../../../helpers/common.helper';
import buyMysterySet from '../../../../website/common/script/ops/buyMysterySet';
import {
NotAuthorized,
NotFound,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyMysterySet', () => {
let user;

View File

@@ -1,12 +1,12 @@
import {
generateUser,
} from '../../helpers/common.helper';
import buyQuest from '../../../website/common/script/ops/buyQuest';
} from '../../../helpers/common.helper';
import buyQuest from '../../../../website/common/script/ops/buyQuest';
import {
NotAuthorized,
NotFound,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyQuest', () => {
let user;

View File

@@ -1,14 +1,14 @@
import buySpecialSpell from '../../../website/common/script/ops/buySpecialSpell';
import buySpecialSpell from '../../../../website/common/script/ops/buySpecialSpell';
import {
BadRequest,
NotFound,
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
import content from '../../../website/common/script/content/index';
} from '../../../helpers/common.helper';
import content from '../../../../website/common/script/content/index';
describe('shared.ops.buySpecialSpell', () => {
let user;

View File

@@ -138,6 +138,7 @@ describe('shared.ops.purchase', () => {
user.balance = userGemAmount;
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = 0;
user.purchased.plan.customerId = 'customer-id';
});
it('purchases gems', () => {
@@ -226,4 +227,39 @@ describe('shared.ops.purchase', () => {
clock.restore();
});
});
context('bulk purchase', () => {
let userGemAmount = 10;
before(() => {
user.balance = userGemAmount;
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = 0;
user.purchased.plan.customerId = 'customer-id';
});
it('makes bulk purchases of gems', () => {
let [, message] = purchase(user, {
params: {type: 'gems', key: 'gem'},
quantity: 2,
});
expect(message).to.equal(i18n.t('plusOneGem'));
expect(user.balance).to.equal(userGemAmount + 0.50);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
it('makes bulk purchases of eggs', () => {
let type = 'eggs';
let key = 'TigerCub';
purchase(user, {
params: {type, key},
quantity: 2,
});
expect(user.items[type][key]).to.equal(2);
});
});
});

View File

@@ -172,13 +172,13 @@ describe('shared.ops.rebirth', () => {
expect(user.flags.levelDrops).to.be.empty;
});
it('does not reset rebirthEnabled if user has beastMaster', () => {
it('reset rebirthEnabled even if user has beastMaster', () => {
user.achievements.beastMaster = 1;
user.flags.rebirthEnabled = true;
rebirth(user);
expect(user.flags.rebirthEnabled).to.be.true;
expect(user.flags.rebirthEnabled).to.be.false;
});
it('sets rebirth achievement', () => {

View File

@@ -19,4 +19,4 @@ echo Update npm...
npm install -g npm@4
echo Installing global modules...
npm install -g gulp bower grunt-cli mocha node-pre-gyp
npm install -g gulp mocha node-pre-gyp

View File

@@ -4,5 +4,5 @@ apt-get install -qq graphicsmagick
echo Installing phantomjs and dependency...
apt-get install -qq libicu48
echo Installing requirements for grunt-spritesmith...
echo Installing requirements for gulp.spritesmith...
apt-get install -qq pkg-config libcairo2-dev libjpeg-dev

View File

@@ -105,7 +105,12 @@ const baseConfig = {
{
test: /\.svg$/,
use: [
{ loader: 'svg-url-loader' },
{
loader: 'svg-url-loader',
options: {
limit: 10000,
},
},
{ loader: 'svgo-loader' },
],
include: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],

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