Compare commits

..

1 Commits

Author SHA1 Message Date
thehollidayinn
e8e4e137ae 4.1.3 2017-10-03 21:17:23 -05:00
7944 changed files with 113161 additions and 26591 deletions

3
.bowerrc Normal file
View File

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

View File

@@ -20,7 +20,11 @@ container_commands:
command: "touch /tmp/.babel.json"
02_ownBabel:
command: "chmod a+rw /tmp/.babel.json"
03_installGulp:
03_installBower:
command: "$NODE_HOME/bin/npm install -g bower"
04_installGulp:
command: "$NODE_HOME/bin/npm install -g gulp"
04_runGulp:
05_runBower:
command: "$NODE_HOME/lib/node_modules/bower/bin/bower --config.interactive=false --allow-root install -f"
06_runGulp:
command: "$NODE_HOME/lib/node_modules/gulp/bin/gulp.js build"

3
.gitignore vendored
View File

@@ -2,14 +2,11 @@
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 mocha
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
@@ -12,6 +12,7 @@ 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,13 +4,14 @@ 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 mocha
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.0.3 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
RUN gulp build:prod --force
# Create Build dir

142
Gruntfile.js Normal file
View File

@@ -0,0 +1,142 @@
/*global module:false*/
require('babel-register');
var _ = require('lodash');
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
karma: {
unit: {
configFile: 'test/client-old/spec/karma.conf.js'
},
continuous: {
configFile: 'test/client-old/spec/karma.conf.js',
singleRun: true,
autoWatch: false
}
},
clean: {
build: ['website/build']
},
cssmin: {
dist: {
options: {
report: 'gzip'
},
files:{
"website/client-old/css/habitrpg-shared.css": [
"website/assets/sprites/dist/spritesmith*.css",
"website/assets/sprites/css/backer.css",
"website/assets/sprites/css/Mounts.css",
"website/assets/sprites/css/index.css"
]
}
}
},
stylus: {
build: {
options: {
compress: false, // AFTER
'include css': true,
paths: ['website/client-old']
},
files: {
'website/build/app.css': ['website/client-old/css/index.styl'],
'website/build/static.css': ['website/client-old/css/static.styl']
}
}
},
copy: {
build: {
files: [
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
]
}
},
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
hashres: {
build: {
options: {
fileNameFormat: '${name}-${hash}.${ext}'
},
src: [
'website/build/*.js',
'website/build/*.css',
'website/build/favicon.ico',
'website/build/favicon_192x192.png',
'website/build/*.png',
'website/build/static/sprites/*.png',
'website/build/*.gif',
'website/build/bower_components/bootstrap/dist/fonts/*'
],
dest: 'website/build/*.css'
}
}
});
//Load build files from client-old/manifest.json
grunt.registerTask('loadManifestFiles', 'Load all build files from client-old/manifest.json', function(){
var files = grunt.file.readJSON('./website/client-old/manifest.json');
var uglify = {};
var cssmin = {};
_.each(files, function(val, key){
var js = uglify['website/build/' + key + '.js'] = [];
_.each(files[key].js, function(val){
var path = "./";
if( val.indexOf('common/') == -1)
path = './website/client-old/';
js.push(path + val);
});
var css = cssmin['website/build/' + key + '.css'] = [];
_.each(files[key].css, function(val){
var path = "./";
if( val.indexOf('common/') == -1) {
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client-old/';
}
css.push(path + val)
});
});
grunt.config.set('uglify.build.files', uglify);
grunt.config.set('uglify.build.options', {compress: false});
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'website/client-old/css/whatever-css.css'});
});
// Register tasks.
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
grunt.registerTask('build:dev', ['cssmin', 'stylus']);
grunt.registerTask('build:test', ['build:dev']);
// Load tasks
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-stylus');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-hashres');
if (process.env.NODE_ENV !== 'production') grunt.loadNpmTasks('grunt-karma');
};

56
bower.json Normal file
View File

@@ -0,0 +1,56 @@
{
"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 = './apidoc_build';
const APIDOC_DEST_PATH = './website/build/apidoc';
const APIDOC_SRC_PATH = './website/server';
gulp.task('apidoc:clean', (done) => {
clean(APIDOC_DEST_PATH, done);

31
gulp/gulp-babelify.js Normal file
View File

@@ -0,0 +1,31 @@
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,10 +2,13 @@ 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');
}
});
@@ -31,8 +34,18 @@ 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',
'apidoc',
]);

10
gulp/gulp-newstuff.js Normal file
View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['armor_mystery_201710','head_mystery_201710']
$each:['shield_mystery_201709','back_mystery_201709']
}
}
};

6175
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.6.1",
"version": "4.1.3",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -14,7 +14,6 @@
"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",
@@ -32,7 +31,8 @@
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "4.0.0-alpha.6",
"bootstrap-vue": "1.0.0-beta.7",
"bootstrap-vue": "^1.0.0-beta.6",
"bower": "~1.3.12",
"browserify": "~12.0.1",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
@@ -52,8 +52,18 @@
"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",
@@ -145,6 +155,11 @@
"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",
@@ -154,7 +169,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 nodemon",
"start": "gulp run:dev",
"postinstall": "gulp build",
"apidoc": "gulp apidoc"
},
@@ -178,6 +193,7 @@
"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,22 +142,4 @@ 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';
xdescribe('GET /export/avatar-:memberId.html', () => {
describe('GET /export/avatar-:memberId.html', () => {
let user;
before(async () => {

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;
@@ -82,19 +82,4 @@ describe('POST /user/buy/:key', () => {
itemText: item.text(),
}));
});
it('allows for bulk purchases', async () => {
await user.update({
'stats.gp': 400,
'stats.hp': 20,
});
let potion = content.potion;
let res = await user.post('/user/buy/potion', {quantity: 2});
await user.sync();
expect(user.stats.hp).to.equal(50);
expect(res.data).to.eql(user.stats);
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
});
});

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

@@ -98,24 +98,4 @@ 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

@@ -0,0 +1,31 @@
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,7 +4,6 @@ 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;
@@ -35,7 +34,6 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -53,7 +51,6 @@ describe('response middleware', () => {
message: 'hello',
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -70,7 +67,6 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: res.locals.user._v,
appVersion: packageInfo.version,
});
});
@@ -85,7 +81,6 @@ describe('response middleware', () => {
data: {field: 1},
notifications: [],
userV: 0,
appVersion: packageInfo.version,
});
});
@@ -109,7 +104,6 @@ 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 healing Rage', () => {
context('with Rage', () => {
beforeEach(async () => {
party.quest.active = false;
party.quest.key = 'trex_undead';
@@ -327,46 +327,6 @@ 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', () => {
@@ -1382,42 +1342,6 @@ 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

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

View File

@@ -0,0 +1,22 @@
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

@@ -0,0 +1,30 @@
'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

@@ -0,0 +1,64 @@
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

@@ -0,0 +1,31 @@
'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

@@ -0,0 +1,125 @@
'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

@@ -0,0 +1,120 @@
'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

@@ -0,0 +1,774 @@
'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

@@ -0,0 +1,121 @@
'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

@@ -0,0 +1,59 @@
'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

@@ -0,0 +1,51 @@
'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

@@ -0,0 +1,86 @@
'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

@@ -0,0 +1,279 @@
'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

@@ -0,0 +1,63 @@
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

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,32 @@
'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

@@ -0,0 +1,50 @@
'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

@@ -0,0 +1,73 @@
'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

@@ -0,0 +1,538 @@
'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

@@ -0,0 +1,257 @@
'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

@@ -0,0 +1,26 @@
'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

@@ -0,0 +1,191 @@
'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

@@ -0,0 +1,489 @@
'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

@@ -0,0 +1,187 @@
'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

@@ -0,0 +1,367 @@
'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

@@ -0,0 +1,41 @@
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

@@ -0,0 +1,76 @@
'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

@@ -0,0 +1,69 @@
'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

@@ -0,0 +1,30 @@
'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

@@ -0,0 +1,89 @@
'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

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,33 @@
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

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,94 @@
'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

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,98 @@
// 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

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

View File

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

View File

@@ -0,0 +1,55 @@
'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

@@ -0,0 +1,282 @@
'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

@@ -0,0 +1,88 @@
'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

@@ -0,0 +1,73 @@
'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

@@ -0,0 +1,200 @@
'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

@@ -0,0 +1,118 @@
'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

@@ -0,0 +1,202 @@
'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

@@ -0,0 +1,435 @@
'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

@@ -0,0 +1,258 @@
'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

@@ -0,0 +1,52 @@
'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

@@ -0,0 +1,440 @@
'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

@@ -0,0 +1,64 @@
'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

@@ -0,0 +1,179 @@
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,15 +25,6 @@ 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]);

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

@@ -0,0 +1,61 @@
/* 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,
});
});
});

View File

@@ -1,124 +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';
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,7 +138,6 @@ 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', () => {
@@ -227,39 +226,4 @@ 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('reset rebirthEnabled even if user has beastMaster', () => {
it('does not reset rebirthEnabled if user has beastMaster', () => {
user.achievements.beastMaster = 1;
user.flags.rebirthEnabled = true;
rebirth(user);
expect(user.flags.rebirthEnabled).to.be.false;
expect(user.flags.rebirthEnabled).to.be.true;
});
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 mocha node-pre-gyp
npm install -g gulp bower grunt-cli 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 gulp.spritesmith...
echo Installing requirements for grunt-spritesmith...
apt-get install -qq pkg-config libcairo2-dev libjpeg-dev

Binary file not shown.

Binary file not shown.

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