describe 'up.util', ->
u = up.util
describe 'JavaScript functions', ->
describe 'up.util.uniq', ->
it 'returns the given array with duplicates elements removed', ->
input = [1, 2, 1, 1, 3]
result = up.util.uniq(input)
expect(result).toEqual [1, 2, 3]
it 'works on DOM elements', ->
one = document.createElement("div")
two = document.createElement("div")
input = [one, one, two, two]
result = up.util.uniq(input)
expect(result).toEqual [one, two]
it 'preserves insertion order', ->
input = [1, 2, 1]
result = up.util.uniq(input)
expect(result).toEqual [1, 2]
describe 'up.util.uniqBy', ->
it 'returns the given array with duplicate elements removed, calling the given function to determine value for uniqueness', ->
input = ["foo", "bar", "apple", 'orange', 'banana']
result = up.util.uniqBy(input, (element) -> element.length)
expect(result).toEqual ['foo', 'apple', 'orange']
it 'accepts a property name instead of a function, which collects that property from each item to compute uniquness', ->
input = ["foo", "bar", "apple", 'orange', 'banana']
result = up.util.uniqBy(input, 'length')
expect(result).toEqual ['foo', 'apple', 'orange']
describe 'up.util.map', ->
it 'creates a new array of values by calling the given function on each item of the given array', ->
array = ["apple", "orange", "cucumber"]
mapped = up.util.map(array, (element) -> element.length)
expect(mapped).toEqual [5, 6, 8]
it 'accepts a property name instead of a function, which collects that property from each item', ->
array = ["apple", "orange", "cucumber"]
mapped = up.util.map(array, 'length')
expect(mapped).toEqual [5, 6, 8]
it 'passes the iteration index as second argument to the given function', ->
array = ["apple", "orange", "cucumber"]
mapped = up.util.map(array, (element, i) -> i)
expect(mapped).toEqual [0, 1, 2]
describe 'up.util.each', ->
it 'calls the given function once for each itm of the given array', ->
args = []
array = ["apple", "orange", "cucumber"]
up.util.each array, (item) -> args.push(item)
expect(args).toEqual ["apple", "orange", "cucumber"]
it 'passes the iteration index as second argument to the given function', ->
args = []
array = ["apple", "orange", "cucumber"]
up.util.each array, (item, index) -> args.push(index)
expect(args).toEqual [0, 1, 2]
describe 'up.util.select', ->
it 'returns an array of those elements in the given array for which the given function returns true', ->
array = ["foo", "orange", "cucumber"]
results = up.util.select array, (item) -> item.length > 3
expect(results).toEqual ['orange', 'cucumber']
it 'passes the iteration index as second argument to the given function', ->
array = ["apple", "orange", "cucumber", "banana"]
results = up.util.select array, (item, index) -> index % 2 == 0
expect(results).toEqual ['apple', 'cucumber']
it 'accepts a property name instead of a function, which checks that property from each item', ->
array = [ { name: 'a', prop: false }, { name: 'b', prop: true } ]
results = up.util.select array, 'prop'
expect(results).toEqual [{ name: 'b', prop: true }]
describe 'up.util.reject', ->
it 'returns an array of those elements in the given array for which the given function returns false', ->
array = ["foo", "orange", "cucumber"]
results = up.util.reject array, (item) -> item.length < 4
expect(results).toEqual ['orange', 'cucumber']
it 'passes the iteration index as second argument to the given function', ->
array = ["apple", "orange", "cucumber", "banana"]
results = up.util.reject array, (item, index) -> index % 2 == 0
expect(results).toEqual ['orange', 'banana']
it 'accepts a property name instead of a function, which checks that property from each item', ->
array = [ { name: 'a', prop: false }, { name: 'b', prop: true } ]
results = up.util.reject array, 'prop'
expect(results).toEqual [{ name: 'a', prop: false }]
describe 'up.util.previewable', ->
it 'wraps a function into a proxy function with an additional .promise attribute', ->
fun = -> 'return value'
proxy = up.util.previewable(fun)
expect(u.isFunction(proxy)).toBe(true)
expect(u.isPromise(proxy.promise)).toBe(true)
expect(proxy()).toEqual('return value')
it "resolves the proxy's .promise when the inner function returns", (done) ->
fun = -> 'return value'
proxy = up.util.previewable(fun)
callback = jasmine.createSpy('promise callback')
proxy.promise.then(callback)
u.nextFrame ->
expect(callback).not.toHaveBeenCalled()
proxy()
u.nextFrame ->
expect(callback).toHaveBeenCalledWith('return value')
done()
it "delays resolution of the proxy's .promise if the inner function returns a promise", (done) ->
funDeferred = u.newDeferred()
fun = -> funDeferred
proxy = up.util.previewable(fun)
callback = jasmine.createSpy('promise callback')
proxy.promise.then(callback)
proxy()
u.nextFrame ->
expect(callback).not.toHaveBeenCalled()
funDeferred.resolve('return value')
u.nextFrame ->
expect(callback).toHaveBeenCalledWith('return value')
done()
describe 'up.util.kebabCase', ->
it 'converts a string of multiple words from camel-case to kebap-case', ->
result = up.util.kebabCase('fooBarBaz')
expect(result).toEqual('foo-bar-baz')
it 'does not change a single word', ->
result = up.util.kebabCase('foo')
expect(result).toEqual('foo')
it 'downcases the first word when it starts with a capital letter', ->
result = up.util.kebabCase('FooBar')
expect(result).toEqual('foo-bar')
it 'does not change a string that is already in kebab-case', ->
result = up.util.kebabCase('foo-bar-baz')
expect(result).toEqual('foo-bar-baz')
describe 'up.util.camelCase', ->
it 'converts a string of multiple words from kebap-case to camel-case', ->
result = up.util.camelCase('foo-bar-baz')
expect(result).toEqual('fooBarBaz')
it 'does not change a single word', ->
result = up.util.camelCase('foo')
expect(result).toEqual('foo')
it 'downcases the first word when it starts with a capital letter', ->
result = up.util.camelCase('Foo-Bar')
expect(result).toEqual('fooBar')
it 'does not change a string that is already in camel-case', ->
result = up.util.camelCase('fooBarBaz')
expect(result).toEqual('fooBarBaz')
describe 'up.util.kebabCaseKeys', ->
it "converts the given object's keys from camel-case to kebab-case", ->
input =
fooBar: 'one'
barBaz: 'two'
result = up.util.kebabCaseKeys(input)
expect(result).toEqual
'foo-bar': 'one'
'bar-baz': 'two'
it "does not change an object whose keys are already kebab-case", ->
input =
'foo-bar': 'one'
'bar-baz': 'two'
result = up.util.kebabCaseKeys(input)
expect(result).toEqual
'foo-bar': 'one'
'bar-baz': 'two'
describe 'up.util.camelCaseKeys', ->
it "converts the given object's keys from kebab-case to camel-case", ->
input =
'foo-bar': 'one'
'bar-baz': 'two'
result = up.util.camelCaseKeys(input)
expect(result).toEqual
fooBar: 'one'
barBaz: 'two'
it "does not change an object whose keys are already camel-case", ->
input =
fooBar: 'one'
barBaz: 'two'
result = up.util.camelCaseKeys(input)
expect(result).toEqual
fooBar: 'one'
barBaz: 'two'
describe 'up.util.DivertibleChain', ->
it "instantiates a task queue whose (2..n)th tasks can be changed by calling '.asap'", (done) ->
chain = new up.util.DivertibleChain()
timer1Spy = jasmine.createSpy('timer1 has been called')
timer1 = ->
timer1Spy()
u.promiseTimer(50)
timer2Spy = jasmine.createSpy('timer2 has been called')
timer2 = ->
timer2Spy()
u.promiseTimer(50)
timer3Spy = jasmine.createSpy('timer3 has been called')
timer3 = ->
timer3Spy()
u.promiseTimer(50)
timer4Spy = jasmine.createSpy('timer4 has been called')
timer4 = ->
timer4Spy()
u.promiseTimer(50)
chain.asap(timer1)
u.nextFrame ->
expect(timer1Spy).toHaveBeenCalled()
chain.asap(timer2)
u.nextFrame ->
# timer2 is still waiting for timer1 to finish
expect(timer2Spy).not.toHaveBeenCalled()
# Override the (2..n)th tasks. This unschedules timer2.
chain.asap(timer3, timer4)
u.setTimer 80, ->
expect(timer2Spy).not.toHaveBeenCalled()
expect(timer3Spy).toHaveBeenCalled()
u.setTimer 70, ->
expect(timer4Spy).toHaveBeenCalled()
done()
describe 'up.util.sequence', ->
it 'combines the given functions into a single function', ->
values = []
one = -> values.push('one')
two = -> values.push('two')
three = -> values.push('three')
sequence = up.util.sequence(one, two, three)
expect(values).toEqual([])
sequence()
expect(values).toEqual(['one', 'two', 'three'])
describe 'up.util.createElementFromHtml', ->
it 'parses a string that contains a serialized HTML document', ->
string = """
document title
line 1
line 2
"""
element = up.util.createElementFromHtml(string)
expect(element.querySelector('head title').textContent).toEqual('document title')
expect(element.querySelector('body').getAttribute('data-env')).toEqual('production')
expect(element.querySelectorAll('body div').length).toBe(2)
expect(element.querySelectorAll('body div')[0].textContent).toEqual('line 1')
expect(element.querySelectorAll('body div')[1].textContent).toEqual('line 2')
it 'parses a string that contains carriage returns (bugfix)', ->
string = """
\r
\r
line
\r
\r
\r
"""
$element = up.util.createElementFromHtml(string)
expect($element.querySelector('body')).toBeGiven()
expect($element.querySelector('body div').textContent).toEqual('line')
it 'does not run forever if a page has a without a (bugfix)', ->
html = """
"""
element = up.util.createElementFromHtml(html)
expect(element.querySelector("title")).toBeMissing()
expect(element.querySelector("h1").textContent).toEqual('Full story')
it 'can parse HTML without a ', ->
html = """
Full story
"""
element = up.util.createElementFromHtml(html)
expect(element.querySelector("title")).toBeMissing()
expect(element.querySelector("h1").textContent).toEqual('Full story')
it 'can parse a HTML fragment without a ', ->
html = """
Full story
"""
element = up.util.createElementFromHtml(html)
expect(element.querySelector("title")).toBeMissing()
expect(element.querySelector("h1").textContent).toEqual('Full story')
describe 'up.util.isFixed', ->
it 'returns true if the given element or one of its ancestors has a "fixed" CSS position', ->
$grandGrandParent = affix('.grand-parent')
$grandParent = $grandGrandParent.affix('.grand-parent')
$parent = $grandParent.affix('.parent')
$child = $parent.affix('.child')
$grandParent.css(position: 'fixed')
expect(up.util.isFixed($child)).toBe(true)
expect(up.util.isFixed($parent)).toBe(true)
expect(up.util.isFixed($grandParent)).toBe(true)
expect(up.util.isFixed($grandGrandParent)).toBe(false)
it 'returns false if the given element and its ancestors all have a non-"fixed" CSS position', ->
$element = affix('.element')
expect(up.util.isFixed($element)).toBe(false)
describe 'up.util.setTimer', ->
it 'calls the given function after waiting the given milliseconds', (done) ->
callback = jasmine.createSpy()
expectNotCalled = -> expect(callback).not.toHaveBeenCalled()
expectCalled = -> expect(callback).toHaveBeenCalled()
up.util.setTimer(100, callback)
expectNotCalled()
setTimeout(expectNotCalled, 50)
setTimeout(expectCalled, 50 + 75)
setTimeout(done, 50 + 75)
describe 'if the delay is zero', ->
it 'calls the given function in the next execution frame', ->
callback = jasmine.createSpy()
up.util.setTimer(0, callback)
expect(callback).not.toHaveBeenCalled()
setTimeout((-> expect(callback).toHaveBeenCalled()), 0)
# describe 'up.util.argNames', ->
#
# it 'returns an array of argument names for the given function', ->
# fun = ($element, data) ->
# expect(up.util.argNames(fun)).toEqual(['$element', 'data'])
describe 'up.util.trim', ->
it 'removes leading and trailing whitespace from the given string', ->
string = "\t\n\r abc \r\n\t"
expect(up.util.trim(string)).toEqual('abc')
describe 'up.util.only', ->
it 'returns a copy of the given object with only the given whitelisted properties', ->
original =
foo: 'foo-value'
bar: 'bar-value'
baz: 'baz-value'
bam: 'bam-value'
whitelisted = up.util.only(original, 'bar', 'bam')
expect(whitelisted).toEqual
bar: 'bar-value'
bam: 'bam-value'
# Show that original did not change
expect(original).toEqual
foo: 'foo-value'
bar: 'bar-value'
baz: 'baz-value'
bam: 'bam-value'
it 'does not add empty keys to the returned object if the given object does not have that key', ->
original =
foo: 'foo-value'
whitelisted = up.util.only(original, 'foo', 'bar')
expect(whitelisted).toHaveOwnProperty('foo')
expect(whitelisted).not.toHaveOwnProperty('bar')
describe 'up.util.readInlineStyle', ->
describe 'with a string as second argument', ->
it 'returns a CSS value string from an inline [style] attribute', ->
$div = affix('div').attr('style', 'background-color: #ff0000')
style = up.util.readInlineStyle($div, 'backgroundColor')
# Browsers convert colors to rgb() values, even IE11
expect(style).toEqual('rgb(255, 0, 0)')
it 'returns a blank value if the element does not have the given property in the [style] attribute', ->
$div = affix('div').attr('style', 'background-color: red')
style = up.util.readInlineStyle($div, 'color')
expect(style).toBeBlank()
it 'returns a blank value the given property is a computed property, but not in the [style] attribute', ->
$div = affix('div[class="red-background"]')
inlineStyle = up.util.readInlineStyle($div, 'backgroundColor')
computedStyle = up.util.readComputedStyle($div, 'backgroundColor')
expect(computedStyle).toEqual('rgb(255, 0, 0)')
expect(inlineStyle).toBeBlank()
describe 'with an array as second argument', ->
it 'returns an object with the given inline [style] properties', ->
$div = affix('div').attr('style', 'background-color: #ff0000; color: #0000ff')
style = up.util.readInlineStyle($div, ['backgroundColor', 'color'])
expect(style).toEqual
backgroundColor: 'rgb(255, 0, 0)'
color: 'rgb(0, 0, 255)'
it 'returns blank keys if the element does not have the given property in the [style] attribute', ->
$div = affix('div').attr('style', 'background-color: #ff0000')
style = up.util.readInlineStyle($div, ['backgroundColor', 'color'])
expect(style).toHaveOwnProperty('color')
expect(style.color).toBeBlank()
it 'returns a blank value the given property is a computed property, but not in the [style] attribute', ->
$div = affix('div[class="red-background"]')
inlineStyleHash = up.util.readInlineStyle($div, ['backgroundColor'])
computedBackground = up.util.readComputedStyle($div, 'backgroundColor')
expect(computedBackground).toEqual('rgb(255, 0, 0)')
expect(inlineStyleHash).toHaveOwnProperty('backgroundColor')
expect(inlineStyleHash.backgroundColor).toBeBlank()
describe 'up.util.writeInlineStyle', ->
it "sets the given style properties as the given element's [style] attribute", ->
$div = affix('div')
up.util.writeInlineStyle($div, { color: 'red', backgroundColor: 'blue' })
style = $div.attr('style')
expect(style).toContain('color: red')
expect(style).toContain('background-color: blue')
it "merges the given style properties into the given element's existing [style] value", ->
$div = affix('div[style="color: red"]')
up.util.writeInlineStyle($div, { backgroundColor: 'blue' })
style = $div.attr('style')
expect(style).toContain('color: red')
expect(style).toContain('background-color: blue')
it "converts the values of known length properties to px values automatically", ->
$div = affix('div')
up.util.writeInlineStyle($div, { paddingTop: 100 })
style = $div.attr('style')
expect(style).toContain('padding-top: 100px')
describe 'up.util.writeTemporaryStyle', ->
it "sets the given inline styles and returns a function that will restore the previous inline styles", ->
$div = affix('div[style="color: red"]')
restore = up.util.writeTemporaryStyle($div, { color: 'blue' })
expect($div.attr('style')).toContain('color: blue')
expect($div.attr('style')).not.toContain('color: red')
restore()
expect($div.attr('style')).not.toContain('color: blue')
expect($div.attr('style')).toContain('color: red')
it "does not restore inherited styles", ->
$div = affix('div[class="red-background"]')
restore = up.util.writeTemporaryStyle($div, { backgroundColor: 'blue' })
expect($div.attr('style')).toContain('background-color: blue')
restore()
expect($div.attr('style')).not.toContain('background-color')
describe 'up.util.except', ->
it 'returns a copy of the given object but omits the given blacklisted properties', ->
original =
foo: 'foo-value'
bar: 'bar-value'
baz: 'baz-value'
bam: 'bam-value'
whitelisted = up.util.except(original, 'foo', 'baz')
expect(whitelisted).toEqual
bar: 'bar-value'
bam: 'bam-value'
# Show that original did not change
expect(original).toEqual
foo: 'foo-value'
bar: 'bar-value'
baz: 'baz-value'
bam: 'bam-value'
describe 'up.util.selectorForElement', ->
it "prefers using the element's 'up-id' attribute to using the element's ID", ->
$element = affix('div[up-id=up-id-value]#id-value')
expect(up.util.selectorForElement($element)).toBe('[up-id="up-id-value"]')
it "prefers using the element's ID to using the element's name", ->
$element = affix('div#id-value[name=name-value]')
expect(up.util.selectorForElement($element)).toBe("#id-value")
it "selects the ID with an attribute selector if the ID contains a slash", ->
$element = affix('div').attr(id: 'foo/bar')
expect(up.util.selectorForElement($element)).toBe('[id="foo/bar"]')
it "selects the ID with an attribute selector if the ID contains a space", ->
$element = affix('div').attr(id: 'foo bar')
expect(up.util.selectorForElement($element)).toBe('[id="foo bar"]')
it "selects the ID with an attribute selector if the ID contains a dot", ->
$element = affix('div').attr(id: 'foo.bar')
expect(up.util.selectorForElement($element)).toBe('[id="foo.bar"]')
it "selects the ID with an attribute selector if the ID contains a quote", ->
$element = affix('div').attr(id: 'foo"bar')
expect(up.util.selectorForElement($element)).toBe('[id="foo\\"bar"]')
it "prefers using the element's tagName + [name] to using the element's classes", ->
$element = affix('input[name=name-value].class1.class2')
expect(up.util.selectorForElement($element)).toBe('input[name="name-value"]')
it "prefers using the element's classes to using the element's ARIA label", ->
$element = affix('div.class1.class2')
expect(up.util.selectorForElement($element)).toBe(".class1.class2")
it "prefers using the element's ARIA label to using the element's tag name", ->
$element = affix('div[aria-label="ARIA label value"]')
expect(up.util.selectorForElement($element)).toBe('[aria-label="ARIA label value"]')
it "uses the element's tag name if no better description is available", ->
$element = affix('div')
expect(up.util.selectorForElement($element)).toBe("div")
it 'escapes quotes in attribute selector values', ->
$element = affix('div')
$element.attr('aria-label', 'foo"bar')
expect(up.util.selectorForElement($element)).toBe('[aria-label="foo\\"bar"]')
describe 'up.util.addTemporaryClass', ->
it 'adds the given class to the given element', ->
$element = affix('.foo.bar')
element = $element.get(0)
expect(element.className).toEqual('foo bar')
up.util.addTemporaryClass(element, 'baz')
expect(element.className).toEqual('foo bar baz')
it 'returns a function that restores the original class', ->
$element = affix('.foo.bar')
element = $element.get(0)
restoreClass = up.util.addTemporaryClass(element, 'baz')
expect(element.className).toEqual('foo bar baz')
restoreClass()
expect(element.className).toEqual('foo bar')
describe 'up.util.castedAttr', ->
it 'returns true if the attribute value is the string "true"', ->
$element = affix('div').attr('foo', 'true')
expect(up.util.castedAttr($element, 'foo')).toBe(true)
it 'returns true if the attribute value is the name of the attribute', ->
$element = affix('div').attr('foo', 'foo')
expect(up.util.castedAttr($element, 'foo')).toBe(true)
it 'returns false if the attribute value is the string "false"', ->
$element = affix('div').attr('foo', 'false')
expect(up.util.castedAttr($element, 'foo')).toBe(false)
it 'returns undefined if the element has no such attribute', ->
$element = affix('div')
expect(up.util.castedAttr($element, 'foo')).toBe(undefined)
it 'returns the attribute value unchanged if the value is some string', ->
$element = affix('div').attr('foo', 'some text')
expect(up.util.castedAttr($element, 'foo')).toBe('some text')
describe 'up.util.any', ->
it 'returns true if an element in the array returns true for the given function', ->
result = up.util.any [null, undefined, 'foo', ''], up.util.isPresent
expect(result).toBe(true)
it 'returns false if no element in the array returns true for the given function', ->
result = up.util.any [null, undefined, ''], up.util.isPresent
expect(result).toBe(false)
it 'short-circuits once an element returns true', ->
count = 0
up.util.any [null, undefined, 'foo', ''], (element) ->
count += 1
up.util.isPresent(element)
expect(count).toBe(3)
describe 'up.util.all', ->
it 'returns true if all element in the array returns true for the given function', ->
result = up.util.all ['foo', 'bar', 'baz'], up.util.isPresent
expect(result).toBe(true)
it 'returns false if an element in the array returns false for the given function', ->
result = up.util.all ['foo', 'bar', null, 'baz'], up.util.isPresent
expect(result).toBe(false)
it 'short-circuits once an element returns false', ->
count = 0
up.util.all ['foo', 'bar', '', 'baz'], (element) ->
count += 1
up.util.isPresent(element)
expect(count).toBe(3)
it 'passes the iteration index as second argument to the given function', ->
array = ["apple", "orange", "cucumber"]
args = []
up.util.all array, (item, index) ->
args.push(index)
true
expect(args).toEqual [0, 1, 2]
it 'accepts a property name instead of a function, which collects that property from each item', ->
allTrue = [ { prop: true }, { prop: true } ]
someFalse = [ { prop: true }, { prop: false } ]
expect(up.util.all(allTrue, 'prop')).toBe(true)
expect(up.util.all(someFalse, 'prop')).toBe(false)
# describe 'up.util.none', ->
#
# it 'returns true if no element in the array returns true for the given function', ->
# result = up.util.none ['foo', 'bar', 'baz'], up.util.isBlank
# expect(result).toBe(true)
#
# it 'returns false if an element in the array returns false for the given function', ->
# result = up.util.none ['foo', 'bar', null, 'baz'], up.util.isBlank
# expect(result).toBe(false)
#
# it 'short-circuits once an element returns true', ->
# count = 0
# up.util.none ['foo', 'bar', '', 'baz'], (element) ->
# count += 1
# up.util.isBlank(element)
# expect(count).toBe(3)
#
# it 'passes the iteration index as second argument to the given function', ->
# array = ["apple", "orange", "cucumber"]
# args = []
# up.util.none array, (item, index) ->
# args.push(index)
# false
# expect(args).toEqual [0, 1, 2]
#
# it 'accepts a property name instead of a function, which collects that property from each item', ->
# allFalse = [ { prop: false }, { prop: false } ]
# someTrue = [ { prop: true }, { prop: false } ]
# expect(up.util.none(allFalse, 'prop')).toBe(true)
# expect(up.util.none(someTrue, 'prop')).toBe(false)
describe 'up.util.any', ->
it 'returns true if at least one element in the array returns true for the given function', ->
result = up.util.any ['', 'bar', null], up.util.isPresent
expect(result).toBe(true)
it 'returns false if no element in the array returns true for the given function', ->
result = up.util.any ['', null, undefined], up.util.isPresent
expect(result).toBe(false)
it 'passes the iteration index as second argument to the given function', ->
array = ["apple", "orange", "cucumber"]
args = []
up.util.any array, (item, index) ->
args.push(index)
false
expect(args).toEqual [0, 1, 2]
it 'accepts a property name instead of a function, which collects that property from each item', ->
someTrue = [ { prop: true }, { prop: false } ]
allFalse = [ { prop: false }, { prop: false } ]
expect(up.util.any(someTrue, 'prop')).toBe(true)
expect(up.util.any(allFalse, 'prop')).toBe(false)
describe 'up.util.isBlank', ->
it 'returns false for false', ->
expect(up.util.isBlank(false)).toBe(false)
it 'returns false for true', ->
expect(up.util.isBlank(true)).toBe(false)
it 'returns true for null', ->
expect(up.util.isBlank(null)).toBe(true)
it 'returns true for undefined', ->
expect(up.util.isBlank(undefined)).toBe(true)
it 'returns true for an empty String', ->
expect(up.util.isBlank('')).toBe(true)
it 'returns false for a String with at least one character', ->
expect(up.util.isBlank('string')).toBe(false)
it 'returns true for an empty array', ->
expect(up.util.isBlank([])).toBe(true)
it 'returns false for an array with at least one element', ->
expect(up.util.isBlank(['element'])).toBe(false)
it 'returns true for an empty jQuery collection', ->
expect(up.util.isBlank($([]))).toBe(true)
it 'returns false for a jQuery collection with at least one element', ->
expect(up.util.isBlank($('body'))).toBe(false)
it 'returns true for an empty object', ->
expect(up.util.isBlank({})).toBe(true)
it 'returns false for a function', ->
expect(up.util.isBlank((->))).toBe(false)
it 'returns true for an object with at least one key', ->
expect(up.util.isBlank({key: 'value'})).toBe(false)
describe 'up.util.normalizeUrl', ->
it 'normalizes a relative path', ->
expect(up.util.normalizeUrl('foo')).toBe("http://#{location.hostname}:#{location.port}/foo")
it 'normalizes an absolute path', ->
expect(up.util.normalizeUrl('/foo')).toBe("http://#{location.hostname}:#{location.port}/foo")
it 'normalizes a full URL', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar')).toBe('http://example.com/foo/bar')
it 'preserves a query string', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar?key=value')).toBe('http://example.com/foo/bar?key=value')
it 'strips a query string with { search: false } option', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar?key=value', search: false)).toBe('http://example.com/foo/bar')
it 'does not strip a trailing slash by default', ->
expect(up.util.normalizeUrl('/foo/')).toEqual("http://#{location.hostname}:#{location.port}/foo/")
it 'normalizes redundant segments', ->
expect(up.util.normalizeUrl('/foo/../foo')).toBe("http://#{location.hostname}:#{location.port}/foo")
it 'strips a #hash by default', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar#fragment')).toBe('http://example.com/foo/bar')
it 'preserves a #hash with { hash: true } option', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar#fragment', hash: true)).toBe('http://example.com/foo/bar#fragment')
it 'puts a #hash behind the query string', ->
expect(up.util.normalizeUrl('http://example.com/foo/bar?key=value#fragment', hash: true)).toBe('http://example.com/foo/bar?key=value#fragment')
describe 'up.util.detect', ->
it 'finds the first element in the given array that matches the given tester', ->
array = ['foo', 'bar', 'baz']
tester = (element) -> element[0] == 'b'
expect(up.util.detect(array, tester)).toEqual('bar')
it "returns undefined if the given array doesn't contain a matching element", ->
array = ['foo', 'bar', 'baz']
tester = (element) -> element[0] == 'z'
expect(up.util.detect(array, tester)).toBeUndefined()
describe 'up.util.config', ->
it 'creates an object with the given attributes', ->
object = up.util.config(a: 1, b: 2)
expect(object.a).toBe(1)
expect(object.b).toBe(2)
it 'does not allow to set a key that was not included in the factory settings', ->
object = up.util.config(a: 1)
object.b = 2
expect(object.b).toBeUndefined()
describe '#reset', ->
it 'resets the object to its original state', ->
object = up.util.config(a: 1)
expect(object.b).toBeUndefined()
object.a = 2
expect(object.a).toBe(2)
object.reset()
expect(object.a).toBe(1)
it 'does not remove the #reset or #update method from the object', ->
object = up.util.config(a: 1)
object.b = 2
object.reset()
expect(object.reset).toBeDefined()
describe 'up.util.remove', ->
it 'removes the given string from the given array', ->
array = ['a', 'b', 'c']
up.util.remove(array, 'b')
expect(array).toEqual ['a', 'c']
it 'removes the given object from the given array', ->
obj1 = { 'key': 1 }
obj2 = { 'key': 2 }
obj3 = { 'key': 3 }
array = [obj1, obj2, obj3]
up.util.remove(array, obj2)
expect(array).toEqual [obj1, obj3]
describe 'up.util.requestDataAsQuery', ->
encodedOpeningBracket = '%5B'
encodedClosingBracket = '%5D'
encodedSpace = '%20'
it 'returns the query section for the given object', ->
string = up.util.requestDataAsQuery('foo-key': 'foo value', 'bar-key': 'bar value')
expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
it 'returns the query section for the given nested object', ->
string = up.util.requestDataAsQuery('foo-key': { 'bar-key': 'bar-value' }, 'bam-key': 'bam-value')
expect(string).toEqual("foo-key#{encodedOpeningBracket}bar-key#{encodedClosingBracket}=bar-value&bam-key=bam-value")
it 'returns the query section for the given array with { name } and { value } keys', ->
string = up.util.requestDataAsQuery([
{ name: 'foo-key', value: 'foo value' },
{ name: 'bar-key', value: 'bar value' }
])
expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
it 'returns a given query string', ->
string = up.util.requestDataAsQuery('foo=bar')
expect(string).toEqual('foo=bar')
it 'strips a leading question mark from the given query string', ->
string = up.util.requestDataAsQuery('?foo=bar')
expect(string).toEqual('foo=bar')
it 'returns an empty string for an empty object', ->
string = up.util.requestDataAsQuery({})
expect(string).toEqual('')
it 'returns an empty string for an empty string', ->
string = up.util.requestDataAsQuery('')
expect(string).toEqual('')
it 'returns an empty string for undefined', ->
string = up.util.requestDataAsQuery(undefined)
expect(string).toEqual('')
it 'URL-encodes characters in the key and value', ->
string = up.util.requestDataAsQuery({ 'äpfel': 'bäume' })
expect(string).toEqual('%C3%A4pfel=b%C3%A4ume')
it 'URL-encodes plus characters', ->
string = up.util.requestDataAsQuery({ 'my+key': 'my+value' })
expect(string).toEqual('my%2Bkey=my%2Bvalue')
describe 'up.util.unresolvablePromise', ->
it 'return a pending promise', (done) ->
promise = up.util.unresolvablePromise()
promiseState(promise).then (result) ->
expect(result.state).toEqual('pending')
done()
it 'returns a different object every time (to prevent memory leaks)', ->
one = up.util.unresolvablePromise()
two = up.util.unresolvablePromise()
expect(one).not.toBe(two)
describe 'up.util.requestDataAsArray', ->
it 'normalized null to an empty array', ->
array = up.util.requestDataAsArray(null)
expect(array).toEqual([])
it 'normalized undefined to an empty array', ->
array = up.util.requestDataAsArray(undefined)
expect(array).toEqual([])
it 'normalizes an object hash to an array of objects with { name } and { value } keys', ->
array = up.util.requestDataAsArray(
'foo-key': 'foo-value'
'bar-key': 'bar-value'
)
expect(array).toEqual([
{ name: 'foo-key', value: 'foo-value' },
{ name: 'bar-key', value: 'bar-value' },
])
it 'normalizes a nested object hash to a flat array using param naming conventions', ->
array = up.util.requestDataAsArray(
'foo-key': 'foo-value'
'bar-key': {
'bam-key': 'bam-value'
'baz-key': {
'qux-key': 'qux-value'
}
}
)
expect(array).toEqual([
{ name: 'foo-key', value: 'foo-value' },
{ name: 'bar-key[bam-key]', value: 'bam-value' },
{ name: 'bar-key[baz-key][qux-key]', value: 'qux-value' },
])
it 'returns a given array without modification', ->
array = up.util.requestDataAsArray([
{ name: 'foo-key', value: 'foo-value' },
{ name: 'bar-key', value: 'bar-value' },
])
expect(array).toEqual([
{ name: 'foo-key', value: 'foo-value' },
{ name: 'bar-key', value: 'bar-value' },
])
it 'does not URL-encode special characters keys or values', ->
array = up.util.requestDataAsArray(
'äpfel': { 'bäume': 'börse' }
)
expect(array).toEqual([
{ name: 'äpfel[bäume]', value: 'börse' },
])
it 'does not URL-encode spaces in keys or values', ->
array = up.util.requestDataAsArray(
'my key': 'my value'
)
expect(array).toEqual([
{ name: 'my key', value: 'my value' },
])
it 'does not URL-encode ampersands in keys or values', ->
array = up.util.requestDataAsArray(
'my&key': 'my&value'
)
expect(array).toEqual([
{ name: 'my&key', value: 'my&value' },
])
it 'does not URL-encode equal signs in keys or values', ->
array = up.util.requestDataAsArray(
'my=key': 'my=value'
)
expect(array).toEqual([
{ name: 'my=key', value: 'my=value' },
])
describe 'up.util.flatten', ->
it 'flattens the given array', ->
array = [1, [2, 3], 4]
expect(u.flatten(array)).toEqual([1, 2, 3, 4])
it 'only flattens one level deep for performance reasons', ->
array = [1, [2, [3,4]], 5]
expect(u.flatten(array)).toEqual([1, 2, [3, 4], 5])
describe 'up.util.renameKey', ->
it 'renames a key in the given property', ->
object = { a: 'a value', b: 'b value'}
u.renameKey(object, 'a', 'c')
expect(object.a).toBeUndefined()
expect(object.b).toBe('b value')
expect(object.c).toBe('a value')
describe 'up.util.selectInSubtree', ->
it 'finds the selector in ancestors and descendants of the given element', ->
$grandMother = affix('.grand-mother.match')
$mother = $grandMother.affix('.mother')
$element = $mother.affix('.element')
$child = $element.affix('.child.match')
$grandChild = $child.affix('.grand-child.match')
$matches = up.util.selectInSubtree($element, '.match')
$expected = $child.add($grandChild)
expect($matches).toEqual $expected
it 'finds the element itself if it matches the selector', ->
$element = affix('.element.match')
$matches = up.util.selectInSubtree($element, '.match')
expect($matches).toEqual $element
describe 'when given a jQuery collection with multiple elements', ->
it 'searches in a all subtrees of the given elements', ->
$a_grandMother = affix('.grand-mother.match')
$a_mother = $a_grandMother.affix('.mother')
$a_element = $a_mother.affix('.element')
$a_child = $a_element.affix('.child.match')
$a_grandChild = $a_child.affix('.grand-child.match')
$b_grandMother = affix('.grand-mother.match')
$b_mother = $b_grandMother.affix('.mother')
$b_element = $b_mother.affix('.element')
$b_child = $b_element.affix('.child.match')
$b_grandChild = $b_child.affix('.grand-child.match')
$matches = up.util.selectInSubtree($a_element.add($b_element), '.match')
expect($matches).toEqual $a_child.add($a_grandChild).add($b_child).add($b_grandChild)
describe 'up.util.selectInDynasty', ->
it 'finds the selector in both ancestors and descendants of the given element', ->
$grandMother = affix('.grand-mother.match')
$mother = $grandMother.affix('.mother')
$element = $mother.affix('.element')
$child = $element.affix('.child.match')
$grandChild = $child.affix('.grand-child.match')
$matches = up.util.selectInDynasty($element, '.match')
$expected = $grandMother.add($child).add($grandChild)
expect($matches).toEqual $expected
it 'finds the element itself if it matches the selector', ->
$element = affix('.element.match')
$matches = up.util.selectInDynasty($element, '.match')
expect($matches).toEqual $element
describe 'up.util.isCrossDomain', ->
it 'returns false for an absolute path', ->
expect(up.util.isCrossDomain('/foo')).toBe(false)
it 'returns false for an relative path', ->
expect(up.util.isCrossDomain('foo')).toBe(false)
it 'returns false for a fully qualified URL with the same protocol and hostname as the current location', ->
fullUrl = "#{location.protocol}//#{location.host}/foo"
expect(up.util.isCrossDomain(fullUrl)).toBe(false)
it 'returns true for a fully qualified URL with a different protocol than the current location', ->
fullUrl = "otherprotocol://#{location.host}/foo"
expect(up.util.isCrossDomain(fullUrl)).toBe(true)
it 'returns false for a fully qualified URL with a different hostname than the current location', ->
fullUrl = "#{location.protocol}//other-host.tld/foo"
expect(up.util.isCrossDomain(fullUrl)).toBe(true)
describe 'up.util.isOptions', ->
it 'returns true for an Object instance', ->
expect(up.util.isOptions(new Object())).toBe(true)
it 'returns true for an object literal', ->
expect(up.util.isOptions({ foo: 'bar'})).toBe(true)
it 'returns false for undefined', ->
expect(up.util.isOptions(undefined)).toBe(false)
it 'returns false for null', ->
expect(up.util.isOptions(null)).toBe(false)
it 'returns false for a function (which is technically an object)', ->
fn = -> 'foo'
fn.key = 'value'
expect(up.util.isOptions(fn)).toBe(false)
it 'returns false for an array', ->
expect(up.util.isOptions(['foo'])).toBe(false)
it 'returns false for a jQuery collection', ->
expect(up.util.isOptions($('body'))).toBe(false)
it 'returns false for a promise', ->
expect(up.util.isOptions(Promise.resolve())).toBe(false)
it 'returns false for a FormData object', ->
expect(up.util.isOptions(new FormData())).toBe(false)
describe 'up.util.isObject', ->
it 'returns true for an Object instance', ->
expect(up.util.isObject(new Object())).toBe(true)
it 'returns true for an object literal', ->
expect(up.util.isObject({ foo: 'bar'})).toBe(true)
it 'returns false for undefined', ->
expect(up.util.isObject(undefined)).toBe(false)
it 'returns false for null', ->
expect(up.util.isObject(null)).toBe(false)
it 'returns true for a function (which is technically an object)', ->
fn = -> 'foo'
fn.key = 'value'
expect(up.util.isObject(fn)).toBe(true)
it 'returns true for an array', ->
expect(up.util.isObject(['foo'])).toBe(true)
it 'returns true for a jQuery collection', ->
expect(up.util.isObject($('body'))).toBe(true)
it 'returns true for a promise', ->
expect(up.util.isObject(Promise.resolve())).toBe(true)
it 'returns true for a FormData object', ->
expect(up.util.isObject(new FormData())).toBe(true)
describe 'up.util.memoize', ->
it 'returns a function that calls the memoized function', ->
fun = (a, b) -> a + b
memoized = u.memoize(fun)
expect(memoized(2, 3)).toEqual(5)
it 'returns the cached return value of the first call when called again', ->
spy = jasmine.createSpy().and.returnValue(5)
memoized = u.memoize(spy)
expect(memoized(2, 3)).toEqual(5)
expect(memoized(2, 3)).toEqual(5)
expect(spy.calls.count()).toEqual(1)
['assign', 'assignPolyfill'].forEach (assignVariant) ->
describe "up.util.#{assignVariant}", ->
assign = up.util[assignVariant]
it 'copies the second object into the first object', ->
target = { a: 1 }
source = { b: 2, c: 3 }
assign(target, source)
expect(target).toEqual { a: 1, b: 2, c: 3 }
# Source is unchanged
expect(source).toEqual { b: 2, c: 3 }
it 'copies null property values', ->
target = { a: 1, b: 2 }
source = { b: null }
assign(target, source)
expect(target).toEqual { a: 1, b: null }
it 'copies undefined property values', ->
target = { a: 1, b: 2 }
source = { b: undefined }
assign(target, source)
expect(target).toEqual { a: 1, b: undefined }
it 'returns the first object', ->
target = { a: 1 }
source = { b: 2 }
result = assign(target, source)
expect(result).toBe(target)
it 'takes multiple sources to copy from', ->
target = { a: 1 }
source1 = { b: 2, c: 3 }
source2 = { d: 4, e: 5 }
assign(target, source1, source2)
expect(target).toEqual { a: 1, b: 2, c: 3, d: 4, e: 5 }