Refactoring tests and moving code to common module
1. All tests have been converted into regular command-line mocha tests (instead of running in the browser), and the deployment script has been changed to automatically fail any `--new` deployment which doesn't pass. 2. Several small class (e.g., `Logger`) have been moved into a common module, `crafting-guide-common` so that they may be shared between the client and server.
This commit is contained in:
+22
-16
@@ -5,6 +5,8 @@
|
|||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
|
fs = require 'fs'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
module.exports = (grunt)->
|
module.exports = (grunt)->
|
||||||
@@ -23,15 +25,6 @@ module.exports = (grunt)->
|
|||||||
files:
|
files:
|
||||||
'./dist/js/main.js': ['./src/coffee/main.coffee']
|
'./dist/js/main.js': ['./src/coffee/main.coffee']
|
||||||
|
|
||||||
test:
|
|
||||||
options:
|
|
||||||
transform: ['coffeeify']
|
|
||||||
browserifyOptions:
|
|
||||||
debug: true
|
|
||||||
extensions: ['.coffee']
|
|
||||||
files:
|
|
||||||
'./dist/js/test.js': ['./src/coffee/test/test.coffee']
|
|
||||||
|
|
||||||
markdown:
|
markdown:
|
||||||
options:
|
options:
|
||||||
browserifyOptions:
|
browserifyOptions:
|
||||||
@@ -74,9 +67,6 @@ module.exports = (grunt)->
|
|||||||
main:
|
main:
|
||||||
files:
|
files:
|
||||||
'./dist/js/main.js.map': ['./dist/js/main.js']
|
'./dist/js/main.js.map': ['./dist/js/main.js']
|
||||||
test:
|
|
||||||
files:
|
|
||||||
'./dist/js/test.js.map': ['./dist/js/test.js']
|
|
||||||
|
|
||||||
jade:
|
jade:
|
||||||
pages:
|
pages:
|
||||||
@@ -103,6 +93,18 @@ module.exports = (grunt)->
|
|||||||
files:
|
files:
|
||||||
'./src/coffee/views.js': ['./src/jade/templates/**/*.jade']
|
'./src/coffee/views.js': ['./src/jade/templates/**/*.jade']
|
||||||
|
|
||||||
|
mochaTest:
|
||||||
|
options:
|
||||||
|
bail: true
|
||||||
|
color: true
|
||||||
|
reporter: 'list'
|
||||||
|
require: [
|
||||||
|
'coffee-script/register'
|
||||||
|
'./test/test_helper.coffee'
|
||||||
|
]
|
||||||
|
verbose: true
|
||||||
|
src: './test/**/*.test.coffee'
|
||||||
|
|
||||||
rsync:
|
rsync:
|
||||||
static:
|
static:
|
||||||
options:
|
options:
|
||||||
@@ -151,14 +153,18 @@ module.exports = (grunt)->
|
|||||||
sass:
|
sass:
|
||||||
files: ['./src/**/*.scss']
|
files: ['./src/**/*.scss']
|
||||||
tasks: ['sass', 'copy:styles']
|
tasks: ['sass', 'copy:styles']
|
||||||
tests:
|
|
||||||
files: ['./src/**/*.coffee', './test/**/*.coffee']
|
|
||||||
tasks: ['browserify:test']
|
|
||||||
|
|
||||||
grunt.registerTask 'default', 'build'
|
grunt.registerTask 'default', 'build'
|
||||||
|
|
||||||
grunt.registerTask 'build', [ 'rsync:non_html', 'copy', 'sass:build', 'jade', 'browserify', 'exorcise' ]
|
grunt.registerTask 'build', [ 'rsync:non_html', 'copy', 'sass:build', 'jade', 'browserify', 'exorcise' ]
|
||||||
|
|
||||||
grunt.registerTask 'dist', ['clean', 'build', 'rsync:static', 'sass:dist', 'uglify']
|
grunt.registerTask 'dist', ['test', 'clean', 'build', 'rsync:static', 'sass:dist', 'uglify']
|
||||||
|
|
||||||
grunt.registerTask 'clean-watch', ['clean', 'build', 'watch']
|
grunt.registerTask 'clean-watch', ['clean', 'build', 'watch']
|
||||||
|
|
||||||
|
grunt.registerTask 'link-deps', ->
|
||||||
|
grunt.file.mkdir './node_modules'
|
||||||
|
fs.unlinkSync './node_modules/crafting-guide-common'
|
||||||
|
fs.symlinkSync '../../crafting-guide-common/', './node_modules/crafting-guide-common'
|
||||||
|
|
||||||
|
grunt.registerTask 'test', ['mochaTest']
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/andrewminer/crafting-guide",
|
"homepage": "https://github.com/andrewminer/crafting-guide",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"chai": "^1.10.0",
|
||||||
|
"coffee-script": "^1.9.1",
|
||||||
"coffeeify": "^1.0.0",
|
"coffeeify": "^1.0.0",
|
||||||
"express": "^4.10.6",
|
"express": "^4.10.6",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
@@ -28,8 +30,12 @@
|
|||||||
"grunt-contrib-uglify": "^0.7.0",
|
"grunt-contrib-uglify": "^0.7.0",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^0.6.1",
|
||||||
"grunt-exorcise": "^0.2.0",
|
"grunt-exorcise": "^0.2.0",
|
||||||
|
"grunt-mocha-test": "^0.12.7",
|
||||||
"grunt-rename": "^0.1.4",
|
"grunt-rename": "^0.1.4",
|
||||||
"grunt-rsync": "^0.6.2",
|
"grunt-rsync": "^0.6.2",
|
||||||
|
"mocha": "^2.2.1",
|
||||||
|
"sinon": "^1.14.1",
|
||||||
|
"sinon-chai": "^2.7.0",
|
||||||
"underscore": "^1.7.0"
|
"underscore": "^1.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+7
-4
@@ -20,10 +20,13 @@ fi
|
|||||||
|
|
||||||
if [[ "$TARGET" == "new" ]]; then
|
if [[ "$TARGET" == "new" ]]; then
|
||||||
find "$ROOT" -name ".DS_Store" | xargs rm
|
find "$ROOT" -name ".DS_Store" | xargs rm
|
||||||
grunt clean dist
|
if grunt dist; then
|
||||||
echo "Preparing to upload to S3..."
|
echo "Preparing to upload to S3..."
|
||||||
s3cmd sync -r --delete-removed --add-header="cache-control:max-age=1800" "$ROOT/" "s3://new.$SITE/"
|
s3cmd sync -r --delete-removed --add-header="cache-control:max-age=1800" "$ROOT/" "s3://new.$SITE/"
|
||||||
grunt clean build
|
grunt clean build
|
||||||
|
else
|
||||||
|
echo "Aborting deployment"
|
||||||
|
fi
|
||||||
elif [[ "$TARGET" == "promote" ]]; then
|
elif [[ "$TARGET" == "promote" ]]; then
|
||||||
aws s3 sync "s3://new.$SITE" "s3://$SITE" --delete
|
aws s3 sync "s3://new.$SITE" "s3://$SITE" --delete
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ All rights reserved.
|
|||||||
|
|
||||||
BaseController = require './base_controller'
|
BaseController = require './base_controller'
|
||||||
CraftingGridController = require './crafting_grid_controller'
|
CraftingGridController = require './crafting_grid_controller'
|
||||||
{Duration} = require '../constants'
|
|
||||||
ImageLoader = require './image_loader'
|
ImageLoader = require './image_loader'
|
||||||
Inventory = require '../models/inventory'
|
Inventory = require '../models/inventory'
|
||||||
InventoryTableController = require './inventory_table_controller'
|
InventoryTableController = require './inventory_table_controller'
|
||||||
StringBuilder = require '../models/string_builder'
|
{Duration} = require '../constants'
|
||||||
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -51,13 +51,10 @@ module.exports = class MarkdownSectionController extends BaseController
|
|||||||
refs = tree[1].references
|
refs = tree[1].references
|
||||||
|
|
||||||
findLinkRefs = (node)=>
|
findLinkRefs = (node)=>
|
||||||
logger.debug "inspecting a #{node[0]}"
|
|
||||||
if node[0] is 'link_ref'
|
if node[0] is 'link_ref'
|
||||||
name = node[2]
|
name = node[2]
|
||||||
logger.debug "found a link_ref for #{name}"
|
|
||||||
item = @modPack.findItemByName node[2]
|
item = @modPack.findItemByName node[2]
|
||||||
if item?
|
if item?
|
||||||
logger.debug "found related item: #{item}"
|
|
||||||
node[0] = 'link'
|
node[0] = 'link'
|
||||||
node[1].href = Url.item itemSlug:item.slug.item, modSlug:item.slug.mod
|
node[1].href = Url.item itemSlug:item.slug.item, modSlug:item.slug.mod
|
||||||
delete node[1].ref
|
delete node[1].ref
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ All rights reserved.
|
|||||||
|
|
||||||
BaseController = require './base_controller'
|
BaseController = require './base_controller'
|
||||||
CraftingGridController = require './crafting_grid_controller'
|
CraftingGridController = require './crafting_grid_controller'
|
||||||
{Duration} = require '../constants'
|
|
||||||
ImageLoader = require './image_loader'
|
ImageLoader = require './image_loader'
|
||||||
StringBuilder = require '../models/string_builder'
|
{Duration} = require '../constants'
|
||||||
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
###
|
|
||||||
Crafting Guide - logger.coffee
|
|
||||||
|
|
||||||
Copyright (c) 2014-2015 by Redwood Labs
|
|
||||||
All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
module.exports = class Logger
|
|
||||||
|
|
||||||
@TRACE = {name:'TRACE ', value:0}
|
|
||||||
@DEBUG = {name:'DEBUG ', value:1}
|
|
||||||
@VERBOSE = {name:'VERBOSE', value:2}
|
|
||||||
@INFO = {name:'INFO ', value:3}
|
|
||||||
@WARNING = {name:'WARNING', value:4}
|
|
||||||
@ERROR = {name:'ERROR ', value:5}
|
|
||||||
@FATAL = {name:'FATAL ', value:6}
|
|
||||||
|
|
||||||
ALL_LEVELS = [@TRACE, @DEBUG, @VERBOSE, @INFO, @WARNING, @ERROR, @FATAL]
|
|
||||||
|
|
||||||
constructor: (options={})->
|
|
||||||
options.level ?= Logger.FATAL
|
|
||||||
@formatText = options.format
|
|
||||||
@formatText ?= "<%= timestamp %> | <%= level %> | <%= indent %><%= message %>"
|
|
||||||
@level = @_parseLevel options
|
|
||||||
|
|
||||||
@_format = _.template @formatText
|
|
||||||
@_indent = ''
|
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
|
||||||
|
|
||||||
indent: ->
|
|
||||||
@_indent += ' '
|
|
||||||
|
|
||||||
log: (level, message)->
|
|
||||||
return unless level.value >= @level.value
|
|
||||||
message = message() if _.isFunction message
|
|
||||||
|
|
||||||
entry = {timestamp:new Date(), level:level, message:message, indent:@_indent}
|
|
||||||
entry.level ?= @level
|
|
||||||
|
|
||||||
lines = @_formatEntry entry
|
|
||||||
if entry.level.value < Logger.WARNING.value
|
|
||||||
console.log(line) for line in lines
|
|
||||||
else
|
|
||||||
console.error(line) for line in lines
|
|
||||||
|
|
||||||
outdent: ->
|
|
||||||
@_indent = @_indent[0...@_indent.length - 4]
|
|
||||||
|
|
||||||
# Log Methods ##################################################################################
|
|
||||||
|
|
||||||
trace: (message)-> @log Logger.TRACE, message
|
|
||||||
|
|
||||||
debug: (message)-> @log Logger.DEBUG, message
|
|
||||||
|
|
||||||
verbose: (message)-> @log Logger.VERBOSE, message
|
|
||||||
|
|
||||||
info: (message)-> @log Logger.INFO, message
|
|
||||||
|
|
||||||
warning: (message)-> @log Logger.WARNING, message
|
|
||||||
|
|
||||||
error: (message)->
|
|
||||||
message = "#{message.stack}" if message.stack?
|
|
||||||
@log Logger.ERROR, message
|
|
||||||
|
|
||||||
fatal: (message)-> @log Logger.FATAL, message
|
|
||||||
|
|
||||||
# Private Methods ##############################################################################
|
|
||||||
|
|
||||||
_formatEntry: (entry, lines=[])->
|
|
||||||
message = entry.message.replace /\\n/g, '\n'
|
|
||||||
for line in message.split '\n'
|
|
||||||
result = []
|
|
||||||
result.push @_format
|
|
||||||
timestamp: "#{entry.timestamp}"
|
|
||||||
level: entry.level.name
|
|
||||||
message: line
|
|
||||||
indent: entry.indent
|
|
||||||
lines.push result.join ''
|
|
||||||
return lines
|
|
||||||
|
|
||||||
_parseLevel: (options)->
|
|
||||||
return Logger.FATAL unless _(options).has 'level'
|
|
||||||
level = options.level
|
|
||||||
|
|
||||||
if not level?
|
|
||||||
candidates = []
|
|
||||||
else if _.isString level
|
|
||||||
candidates = (l for l in ALL_LEVELS when l.name.trim().toLowerCase() is level.trim().toLowerCase())
|
|
||||||
else if _.isNumber level
|
|
||||||
candidates = (l for l in ALL_LEVELS when l.value is level)
|
|
||||||
else if level?
|
|
||||||
candidates = (l for l in ALL_LEVELS when l is level)
|
|
||||||
|
|
||||||
throw new Error "invalid level: #{level}" unless candidates.length > 0
|
|
||||||
return candidates[0]
|
|
||||||
@@ -8,10 +8,10 @@ All rights reserved.
|
|||||||
require './underscore_mixins'
|
require './underscore_mixins'
|
||||||
require './polyfill'
|
require './polyfill'
|
||||||
|
|
||||||
views = require './views'
|
|
||||||
FeedbackController = require './controllers/feedback_controller'
|
|
||||||
Logger = require './logger'
|
|
||||||
CraftingGuideRouter = require './crafting_guide_router'
|
CraftingGuideRouter = require './crafting_guide_router'
|
||||||
|
FeedbackController = require './controllers/feedback_controller'
|
||||||
|
views = require './views'
|
||||||
|
{Logger} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseModel = require './base_model'
|
||||||
{Event} = require '../constants'
|
ItemSlug = require './item_slug'
|
||||||
ItemSlug = require './item_slug'
|
Recipe = require './recipe'
|
||||||
Recipe = require './recipe'
|
{Event} = require '../constants'
|
||||||
StringBuilder = require './string_builder'
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
{Url} = require '../constants'
|
{Url} = require '../constants'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
StringBuilder = require '../../models/string_builder'
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ItemSlug = require '../item_slug'
|
|||||||
ModVersion = require '../mod_version'
|
ModVersion = require '../mod_version'
|
||||||
Recipe = require '../recipe'
|
Recipe = require '../recipe'
|
||||||
Stack = require '../simple_stack'
|
Stack = require '../simple_stack'
|
||||||
StringBuilder = require '../string_builder'
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseModel = require './base_model'
|
||||||
{Event} = require '../constants'
|
Stack = require './stack'
|
||||||
Stack = require './stack'
|
{Event} = require '../constants'
|
||||||
StringBuilder = require './string_builder'
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -119,7 +119,8 @@ module.exports = class Recipe extends BaseModel
|
|||||||
actuallyProduces = not @isPassThroughFor stack.itemSlug
|
actuallyProduces = not @isPassThroughFor stack.itemSlug
|
||||||
@_produces[stack.itemSlug.qualified] = actuallyProduces
|
@_produces[stack.itemSlug.qualified] = actuallyProduces
|
||||||
|
|
||||||
return @_produces[itemSlug.qualified] or @_produces[itemSlug.item]
|
result = @_produces[itemSlug.qualified] or @_produces[itemSlug.item]
|
||||||
|
return result
|
||||||
|
|
||||||
requires: (itemSlug)->
|
requires: (itemSlug)->
|
||||||
for stack in @input
|
for stack in @input
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
###
|
|
||||||
Crafting Guide - string_builder.coffee
|
|
||||||
|
|
||||||
Copyright (c) 2015 by Redwood Labs
|
|
||||||
All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
module.exports = class StringBuilder
|
|
||||||
|
|
||||||
constructor: (options={})->
|
|
||||||
options.indent ?= 0
|
|
||||||
options.indentString ?= ' '
|
|
||||||
|
|
||||||
@context = options.context
|
|
||||||
|
|
||||||
@_initialIndent = options.indent
|
|
||||||
@_indent = options.indent
|
|
||||||
@_indentString = options.indentString
|
|
||||||
@_pieces = []
|
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
|
||||||
|
|
||||||
call: ->
|
|
||||||
args = (arg for arg in arguments)
|
|
||||||
callback = args.pop()
|
|
||||||
args.unshift this
|
|
||||||
return unless _.isFunction callback
|
|
||||||
|
|
||||||
callback.apply null, args
|
|
||||||
|
|
||||||
clear: ->
|
|
||||||
@_indent = @_initialIndent
|
|
||||||
@_pieces = []
|
|
||||||
return this
|
|
||||||
|
|
||||||
indent: ->
|
|
||||||
@_indent += 1
|
|
||||||
return this
|
|
||||||
|
|
||||||
line: (args...)->
|
|
||||||
@push.apply(this, args) if args.length > 0
|
|
||||||
@push '\n'
|
|
||||||
|
|
||||||
loop: (list, options={})->
|
|
||||||
options.start ?= ''
|
|
||||||
options.end ?= ''
|
|
||||||
options.indent ?= false
|
|
||||||
options.delimiter ?= ', '
|
|
||||||
options.onEach ?= (builder, item)-> builder.push item
|
|
||||||
|
|
||||||
@_pushText options.start
|
|
||||||
if options.indent then @indent()
|
|
||||||
|
|
||||||
isFirst = true
|
|
||||||
for item in list
|
|
||||||
if not isFirst then @push options.delimiter
|
|
||||||
isFirst = false
|
|
||||||
options.onEach this, item
|
|
||||||
|
|
||||||
if options.indent then @outdent()
|
|
||||||
@_pushText options.end
|
|
||||||
|
|
||||||
return this
|
|
||||||
|
|
||||||
onlyIf: (condition, callback=null)->
|
|
||||||
return this unless condition
|
|
||||||
|
|
||||||
callback ?= (builder)-> # do nothing
|
|
||||||
callback this
|
|
||||||
return this
|
|
||||||
|
|
||||||
outdent: ->
|
|
||||||
@_indent -= 1
|
|
||||||
return this
|
|
||||||
|
|
||||||
push: ->
|
|
||||||
for i in [0...arguments.length]
|
|
||||||
arg = arguments[i]
|
|
||||||
|
|
||||||
if _.isArray arg
|
|
||||||
@push.apply this, arg
|
|
||||||
else if _.isString arg
|
|
||||||
@_pushText arg
|
|
||||||
else
|
|
||||||
@_pushText "#{arg}"
|
|
||||||
|
|
||||||
return this
|
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
|
||||||
|
|
||||||
toString: ->
|
|
||||||
return @_pieces.join ''
|
|
||||||
|
|
||||||
# Private Methods ##############################################################################
|
|
||||||
|
|
||||||
_pushIndent: ->
|
|
||||||
for i in [0...@_indent]
|
|
||||||
@_pieces.push @_indentString
|
|
||||||
|
|
||||||
_pushText: (text)->
|
|
||||||
return if text.length is 0
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
if @_pieces.length > 0
|
|
||||||
lastPiece = @_pieces[@_pieces.length-1]
|
|
||||||
if lastPiece[lastPiece.length-1] is '\n'
|
|
||||||
@_pushIndent()
|
|
||||||
|
|
||||||
while true
|
|
||||||
newLineAt = text.indexOf '\n', index
|
|
||||||
break if newLineAt is -1
|
|
||||||
|
|
||||||
@_pieces.push text[index..newLineAt]
|
|
||||||
index = newLineAt + 1
|
|
||||||
|
|
||||||
break if newLineAt is text.length - 1
|
|
||||||
@_pushIndent()
|
|
||||||
|
|
||||||
if text.length > index
|
|
||||||
@_pieces.push text[index...text.length]
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
###
|
|
||||||
Crafting Guide - string_builder.test.coffee
|
|
||||||
|
|
||||||
Copyright (c) 2015 by Redwood Labs
|
|
||||||
All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
StringBuilder = require '../models/string_builder'
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
builder = null
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
describe 'string_builder.coffee', ->
|
|
||||||
|
|
||||||
beforeEach -> builder = new StringBuilder
|
|
||||||
|
|
||||||
describe 'call', ->
|
|
||||||
|
|
||||||
it 'works with no arguments', ->
|
|
||||||
builder.call (b)-> b.push 'foo'
|
|
||||||
builder.toString().should.equal 'foo'
|
|
||||||
|
|
||||||
it 'works with multiple arguments', ->
|
|
||||||
builder.call 'foo', 'bar', 'baz', (builder, a, b, c)-> builder.loop [a, b, c]
|
|
||||||
builder.toString().should.equal 'foo, bar, baz'
|
|
||||||
|
|
||||||
describe 'loop', ->
|
|
||||||
|
|
||||||
it 'can make an empty list', ->
|
|
||||||
builder.loop [], start:'[', end:']'
|
|
||||||
builder.toString().should.equal '[]'
|
|
||||||
|
|
||||||
it 'can make a list with a single element', ->
|
|
||||||
builder.loop ['foo'], start:'[', end:']'
|
|
||||||
builder.toString().should.equal '[foo]'
|
|
||||||
|
|
||||||
it 'can make a list with many elements', ->
|
|
||||||
builder.loop ['foo', 'bar', 'baz'], start:'[', end:']'
|
|
||||||
builder.toString().should.equal '[foo, bar, baz]'
|
|
||||||
|
|
||||||
it 'can make a list with a custom callback', ->
|
|
||||||
builder.loop ['foo', 'bar', 'baz'], start:'[', end:']', onEach:(b, i)-> b.push "\"#{i}\""
|
|
||||||
builder.toString().should.equal '["foo", "bar", "baz"]'
|
|
||||||
|
|
||||||
it 'can use a custom delimiter', ->
|
|
||||||
builder.loop ['foo', 'bar', 'baz'], delimiter:'|'
|
|
||||||
builder.toString().should.equal 'foo|bar|baz'
|
|
||||||
|
|
||||||
it 'can indent content', ->
|
|
||||||
builder.loop ['foo', 'bar', 'baz'], start:'[\n', end:'\n]', delimiter:',\n', indent:true
|
|
||||||
builder.toString().should.equal '[\n foo,\n bar,\n baz\n]'
|
|
||||||
|
|
||||||
describe 'onlyIf', ->
|
|
||||||
|
|
||||||
it 'calls the callback on true', ->
|
|
||||||
builder.onlyIf true, (b)-> b.push 'foo'
|
|
||||||
builder.toString().should.equal 'foo'
|
|
||||||
|
|
||||||
describe 'push', ->
|
|
||||||
|
|
||||||
it 'can build a simple string', ->
|
|
||||||
builder.push('foo').push(' bar').push(' baz')
|
|
||||||
builder.toString().should.equal 'foo bar baz'
|
|
||||||
|
|
||||||
it 'can build a multi-line string', ->
|
|
||||||
builder.push('foo').push('\nbar\n').push('baz')
|
|
||||||
builder.toString().should.equal 'foo\nbar\nbaz'
|
|
||||||
|
|
||||||
it 'can build an indented multi-line string', ->
|
|
||||||
builder
|
|
||||||
.push 'foo\n'
|
|
||||||
.indent()
|
|
||||||
.push 'bar\n'
|
|
||||||
.outdent()
|
|
||||||
.push 'baz'
|
|
||||||
|
|
||||||
builder.toString().should.equal 'foo\n bar\nbaz'
|
|
||||||
|
|
||||||
it 'can pick apart multiple newlines in a single chunk', ->
|
|
||||||
builder.indent().push('foo\nbar\nbaz').outdent().push('\nbif')
|
|
||||||
builder.toString().should.equal 'foo\n bar\n baz\nbif'
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
###
|
|
||||||
# Crafting Guide - test.coffee
|
|
||||||
#
|
|
||||||
# Copyright (c) 2014-2015 by Redwood Labs
|
|
||||||
# All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
# Test Set-up ##########################################################################################################
|
|
||||||
|
|
||||||
chai.use require 'sinon-chai'
|
|
||||||
chai.config.includeStack = true
|
|
||||||
|
|
||||||
if typeof(global) is 'undefined'
|
|
||||||
window.global = window
|
|
||||||
|
|
||||||
global.assert = chai.assert
|
|
||||||
global.expect = chai.expect
|
|
||||||
global.should = chai.should()
|
|
||||||
global.util = require 'util'
|
|
||||||
|
|
||||||
Logger = require '../logger'
|
|
||||||
global.logger = new Logger level:Logger.DEBUG
|
|
||||||
|
|
||||||
require '../polyfill'
|
|
||||||
require '../underscore_mixins'
|
|
||||||
|
|
||||||
# Test Registry ########################################################################################################
|
|
||||||
|
|
||||||
mocha.setup 'bdd'
|
|
||||||
|
|
||||||
# tests are roughly in order of how errors should be tackled
|
|
||||||
require './string_builder.test'
|
|
||||||
require './item_slug.test'
|
|
||||||
require './inventory.test'
|
|
||||||
require './recipe.test'
|
|
||||||
require './mod_version.test'
|
|
||||||
require './mod.test'
|
|
||||||
require './mod_pack.test'
|
|
||||||
require './parser_versions/command_parser_version_base.test'
|
|
||||||
require './parser_versions/mod_version_parser_v1.test'
|
|
||||||
require './crafting_plan.test'
|
|
||||||
|
|
||||||
mocha.checkLeaks()
|
|
||||||
mocha.globals ['LiveReload']
|
|
||||||
mocha.run()
|
|
||||||
@@ -32,5 +32,5 @@
|
|||||||
.action
|
.action
|
||||||
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
|
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
|
||||||
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
|
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
|
||||||
a(href="/test.html", rel="nofollow")
|
|
||||||
.divider.bottom
|
.divider.bottom
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
//-
|
|
||||||
//- Crafting Guide - test.jade
|
|
||||||
//-
|
|
||||||
//- Copyright (c) 2014-2015 by Redwood Labs
|
|
||||||
//- All rights reserved.
|
|
||||||
//-
|
|
||||||
|
|
||||||
doctype html
|
|
||||||
html
|
|
||||||
head
|
|
||||||
title Crafting Guide Unit Tests
|
|
||||||
meta(charset="UTF-8")
|
|
||||||
link(href="/images/favicon.png", rel="icon", type="image/png")
|
|
||||||
|
|
||||||
block styles
|
|
||||||
link(rel="stylesheet", type="text/css", href="/css/main.css")
|
|
||||||
link(rel="stylesheet", type="text/css", href="css/mocha.css")
|
|
||||||
style #mocha-stats { position: absolute; top: 12em; }
|
|
||||||
|
|
||||||
body
|
|
||||||
block body
|
|
||||||
.view__feedback
|
|
||||||
.view__screen
|
|
||||||
|
|
||||||
.content
|
|
||||||
include ./includes/_header.jade
|
|
||||||
.page
|
|
||||||
#mocha
|
|
||||||
include ./includes/_footer.jade
|
|
||||||
|
|
||||||
block scripts
|
|
||||||
include ./includes/_standard_scripts.jade
|
|
||||||
script(src="/js/mocha.js")
|
|
||||||
script(src="/js/chai.js")
|
|
||||||
script(src="/js/test.js")
|
|
||||||
@@ -5,11 +5,11 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
CraftingPlan = require '../models/crafting_plan'
|
CraftingPlan = require '../src/coffee/models/crafting_plan'
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
Mod = require '../models/mod'
|
Mod = require '../src/coffee/models/mod'
|
||||||
ModPack = require '../models/mod_pack'
|
ModPack = require '../src/coffee/models/mod_pack'
|
||||||
ModVersion = require '../models/mod_version'
|
ModVersion = require '../src/coffee/models/mod_version'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -5,11 +5,11 @@
|
|||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
{Event} = require '../constants'
|
{Event} = require '../src/coffee/constants'
|
||||||
EventRecorder = require './event_recorder'
|
EventRecorder = require './event_recorder'
|
||||||
Inventory = require '../models/inventory'
|
Inventory = require '../src/coffee/models/inventory'
|
||||||
Item = require '../models/item'
|
Item = require '../src/coffee/models/item'
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
Mod = require '../models/mod'
|
Mod = require '../src/coffee/models/mod'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -5,11 +5,11 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
Item = require '../models/item'
|
Item = require '../src/coffee/models/item'
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
Mod = require '../models/mod'
|
Mod = require '../src/coffee/models/mod'
|
||||||
ModPack = require '../models/mod_pack'
|
ModPack = require '../src/coffee/models/mod_pack'
|
||||||
ModVersion = require '../models/mod_version'
|
ModVersion = require '../src/coffee/models/mod_version'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
Item = require '../models/item'
|
Item = require '../src/coffee/models/item'
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
ModVersion = require '../models/mod_version'
|
ModVersion = require '../src/coffee/models/mod_version'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -92,5 +92,5 @@ describe 'mod_version.coffee', ->
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
it 'finds all recipes which list item as output', ->
|
it 'finds all recipes which list item as output', ->
|
||||||
recipes = modVersion.findRecipes ItemSlug.slugify('Bucket')
|
recipes = modVersion.findRecipes ItemSlug.slugify('test__bucket')
|
||||||
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
|
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
|
||||||
+1
-1
@@ -5,7 +5,7 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
CommandParserVersionBase = require '../../models/parser_versions/command_parser_version_base'
|
CommandParserVersionBase = require '../../src/coffee/models/parser_versions/command_parser_version_base'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
+19
-17
@@ -5,10 +5,11 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
CommandParserVersionBase = require '../../models/parser_versions/command_parser_version_base'
|
CommandParserVersionBase = require '../../src/coffee/models/parser_versions/command_parser_version_base'
|
||||||
ItemSlug = require '../../models/item_slug'
|
ItemSlug = require '../../src/coffee/models/item_slug'
|
||||||
ModVersion = require '../../models/mod_version'
|
ModVersion = require '../../src/coffee/models/mod_version'
|
||||||
ModVersionParserV1 = require '../../models/parser_versions/mod_version_parser_v1'
|
ModVersionParserV1 = require '../../src/coffee/models/parser_versions/mod_version_parser_v1'
|
||||||
|
{Logger} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
recipe:; input:Alpha; pattern:... .0. ...;
|
recipe:; input:Alpha; pattern:... .0. ...;
|
||||||
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
recipe:; input:Bravo; pattern:... 0.0 ...;"
|
||||||
modVersion = parser.parse recipes
|
modVersion = parser.parse recipes
|
||||||
recipes = modVersion.findRecipes ItemSlug.slugify 'charlie'
|
recipes = modVersion.findRecipes ItemSlug.slugify 'test__charlie'
|
||||||
recipes[0].input[0].itemSlug.qualified.should.equal 'alpha'
|
recipes[0].input[0].itemSlug.qualified.should.equal 'alpha'
|
||||||
recipes[1].input[0].itemSlug.qualified.should.equal 'bravo'
|
recipes[1].input[0].itemSlug.qualified.should.equal 'bravo'
|
||||||
|
|
||||||
@@ -68,9 +69,10 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
describe 'input', ->
|
describe 'input', ->
|
||||||
|
|
||||||
it 'adds "input" when present', ->
|
it 'adds "input" when present', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
|
logger.doAtLevel 'DEBUG', ->
|
||||||
slugs = (s.itemSlug.item for s in modVersion.findRecipes(ItemSlug.slugify('charlie'))[0].input)
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: ... 010 ...'
|
||||||
slugs.should.eql ['alpha', 'bravo', 'charlie']
|
slugs = (s.itemSlug.item for s in modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].input)
|
||||||
|
slugs.should.eql ['alpha', 'bravo']
|
||||||
|
|
||||||
it 'requires an "input" declaration', ->
|
it 'requires an "input" declaration', ->
|
||||||
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
|
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
|
||||||
@@ -92,7 +94,7 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
|
|
||||||
it 'adds "pattern" when present', ->
|
it 'adds "pattern" when present', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
|
||||||
modVersion.findRecipes(ItemSlug.slugify('charlie'))[0].pattern.should.equal '... .0. .1.'
|
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].pattern.should.equal '... .0. .1.'
|
||||||
|
|
||||||
it 'requires a "pattern" declaration', ->
|
it 'requires a "pattern" declaration', ->
|
||||||
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
|
||||||
@@ -119,8 +121,8 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
expect(func).to.throw Error, 'Bravo is an input'
|
expect(func).to.throw Error, 'Bravo is an input'
|
||||||
|
|
||||||
it 'computes the input stack sizes from the pattern', ->
|
it 'computes the input stack sizes from the pattern', ->
|
||||||
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
|
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Delta; pattern:111 .0. 2.2'
|
||||||
recipe = modVersion.findRecipes(ItemSlug.slugify('charlie'))[0]
|
recipe = modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0]
|
||||||
recipe.input[0].quantity.should.equal 1
|
recipe.input[0].quantity.should.equal 1
|
||||||
recipe.input[1].quantity.should.equal 3
|
recipe.input[1].quantity.should.equal 3
|
||||||
recipe.input[2].quantity.should.equal 2
|
recipe.input[2].quantity.should.equal 2
|
||||||
@@ -136,7 +138,7 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
|
|
||||||
it 'adds "quantity" when present', ->
|
it 'adds "quantity" when present', ->
|
||||||
modVersion = parser.parse baseText + 'quantity: 2'
|
modVersion = parser.parse baseText + 'quantity: 2'
|
||||||
modVersion.findRecipes(ItemSlug.slugify('charlie'))[0].output[0].quantity.should.equal 2
|
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 2
|
||||||
|
|
||||||
it 'does not allow a duplicate "quantity" declaration', ->
|
it 'does not allow a duplicate "quantity" declaration', ->
|
||||||
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
func = -> parser.parse baseText + 'quantity:1; quantity:2'
|
||||||
@@ -148,7 +150,7 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
|
|
||||||
it 'assumes a quantity of 1 by default', ->
|
it 'assumes a quantity of 1 by default', ->
|
||||||
modVersion = parser.parse baseText
|
modVersion = parser.parse baseText
|
||||||
modVersion.findRecipes(ItemSlug.slugify('charlie'))[0].output[0].quantity.should.equal 1
|
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 1
|
||||||
|
|
||||||
it 'does not allow "quantity" before recipe', ->
|
it 'does not allow "quantity" before recipe', ->
|
||||||
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
|
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
|
||||||
@@ -161,13 +163,13 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
|
|
||||||
it 'adds a single item as the default output', ->
|
it 'adds a single item as the default output', ->
|
||||||
modVersion = parser.parse baseText
|
modVersion = parser.parse baseText
|
||||||
stack = modVersion.findRecipes(ItemSlug.slugify('bravo'))[0].output[0]
|
stack = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output[0]
|
||||||
stack.itemSlug.qualified.should.equal 'test__bravo'
|
stack.itemSlug.qualified.should.equal 'test__bravo'
|
||||||
stack.quantity.should.equal 1
|
stack.quantity.should.equal 1
|
||||||
|
|
||||||
it 'can add multiple extras with quantities', ->
|
it 'can add multiple extras with quantities', ->
|
||||||
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
|
||||||
output = modVersion.findRecipes(ItemSlug.slugify('bravo'))[0].output
|
output = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output
|
||||||
output[0].itemSlug.qualified.should.equal 'test__bravo'
|
output[0].itemSlug.qualified.should.equal 'test__bravo'
|
||||||
output[0].quantity.should.equal 1
|
output[0].quantity.should.equal 1
|
||||||
output[1].itemSlug.qualified.should.equal 'test__delta'
|
output[1].itemSlug.qualified.should.equal 'test__delta'
|
||||||
@@ -196,11 +198,11 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
|
|
||||||
it 'can add a single tool', ->
|
it 'can add a single tool', ->
|
||||||
modVersion = parser.parse baseText + 'tools: Furnace'
|
modVersion = parser.parse baseText + 'tools: Furnace'
|
||||||
modVersion.findRecipes(ItemSlug.slugify('bravo'))[0].tools[0].itemSlug.item.should.equal 'furnace'
|
modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools[0].itemSlug.item.should.equal 'furnace'
|
||||||
|
|
||||||
it 'can add multiple tools', ->
|
it 'can add multiple tools', ->
|
||||||
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
|
||||||
tools = modVersion.findRecipes(ItemSlug.slugify('bravo'))[0].tools
|
tools = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools
|
||||||
tools[0].itemSlug.item.should.equal 'crafting_table'
|
tools[0].itemSlug.item.should.equal 'crafting_table'
|
||||||
tools[1].itemSlug.item.should.equal 'furnace'
|
tools[1].itemSlug.item.should.equal 'furnace'
|
||||||
|
|
||||||
@@ -5,10 +5,10 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
Item = require '../models/item'
|
Item = require '../src/coffee/models/item'
|
||||||
ItemSlug = require '../models/item_slug'
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
Recipe = require '../models/recipe'
|
Recipe = require '../src/coffee/models/recipe'
|
||||||
Stack = require '../models/stack'
|
Stack = require '../src/coffee/models/stack'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - test.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
# Test Set-up ##########################################################################################################
|
||||||
|
|
||||||
|
chai = require 'chai'
|
||||||
|
chai.use require 'sinon-chai'
|
||||||
|
chai.config.includeStack = true
|
||||||
|
|
||||||
|
global._ = require 'underscore'
|
||||||
|
global.Backbone = require 'backbone'
|
||||||
|
global.assert = chai.assert
|
||||||
|
global.expect = chai.expect
|
||||||
|
global.should = chai.should()
|
||||||
|
global.util = require 'util'
|
||||||
|
global.w = require 'when'
|
||||||
|
|
||||||
|
{Logger} = require 'crafting-guide-common'
|
||||||
|
global.logger = new Logger level:Logger.FATAL
|
||||||
|
|
||||||
|
require '../src/coffee/polyfill'
|
||||||
|
require '../src/coffee/underscore_mixins'
|
||||||
Reference in New Issue
Block a user