');
1454 | } else {
1455 | self.format(obj[property]);
1456 | }
1457 | });
1458 |
1459 | this.append(' }');
1460 | };
1461 |
1462 | StringPrettyPrinter.prototype.append = function(value) {
1463 | this.string += value;
1464 | };
1465 |
1466 | return function(value) {
1467 | var stringPrettyPrinter = new StringPrettyPrinter();
1468 | stringPrettyPrinter.format(value);
1469 | return stringPrettyPrinter.string;
1470 | };
1471 | };
1472 |
1473 | getJasmineRequireObj().QueueRunner = function() {
1474 |
1475 | function QueueRunner(attrs) {
1476 | this.fns = attrs.fns || [];
1477 | this.onComplete = attrs.onComplete || function() {};
1478 | this.clearStack = attrs.clearStack || function(fn) {fn();};
1479 | this.onException = attrs.onException || function() {};
1480 | this.catchException = attrs.catchException || function() { return true; };
1481 | this.userContext = {};
1482 | }
1483 |
1484 | QueueRunner.prototype.execute = function() {
1485 | this.run(this.fns, 0);
1486 | };
1487 |
1488 | QueueRunner.prototype.run = function(fns, recursiveIndex) {
1489 | var length = fns.length,
1490 | self = this,
1491 | iterativeIndex;
1492 |
1493 | for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
1494 | var fn = fns[iterativeIndex];
1495 | if (fn.length > 0) {
1496 | return attemptAsync(fn);
1497 | } else {
1498 | attemptSync(fn);
1499 | }
1500 | }
1501 |
1502 | var runnerDone = iterativeIndex >= length;
1503 |
1504 | if (runnerDone) {
1505 | this.clearStack(this.onComplete);
1506 | }
1507 |
1508 | function attemptSync(fn) {
1509 | try {
1510 | fn.call(self.userContext);
1511 | } catch (e) {
1512 | handleException(e);
1513 | }
1514 | }
1515 |
1516 | function attemptAsync(fn) {
1517 | var next = function () { self.run(fns, iterativeIndex + 1); };
1518 |
1519 | try {
1520 | fn.call(self.userContext, next);
1521 | } catch (e) {
1522 | handleException(e);
1523 | next();
1524 | }
1525 | }
1526 |
1527 | function handleException(e) {
1528 | self.onException(e);
1529 | if (!self.catchException(e)) {
1530 | //TODO: set a var when we catch an exception and
1531 | //use a finally block to close the loop in a nice way..
1532 | throw e;
1533 | }
1534 | }
1535 | };
1536 |
1537 | return QueueRunner;
1538 | };
1539 |
1540 | getJasmineRequireObj().ReportDispatcher = function() {
1541 | function ReportDispatcher(methods) {
1542 |
1543 | var dispatchedMethods = methods || [];
1544 |
1545 | for (var i = 0; i < dispatchedMethods.length; i++) {
1546 | var method = dispatchedMethods[i];
1547 | this[method] = (function(m) {
1548 | return function() {
1549 | dispatch(m, arguments);
1550 | };
1551 | }(method));
1552 | }
1553 |
1554 | var reporters = [];
1555 |
1556 | this.addReporter = function(reporter) {
1557 | reporters.push(reporter);
1558 | };
1559 |
1560 | return this;
1561 |
1562 | function dispatch(method, args) {
1563 | for (var i = 0; i < reporters.length; i++) {
1564 | var reporter = reporters[i];
1565 | if (reporter[method]) {
1566 | reporter[method].apply(reporter, args);
1567 | }
1568 | }
1569 | }
1570 | }
1571 |
1572 | return ReportDispatcher;
1573 | };
1574 |
1575 |
1576 | getJasmineRequireObj().SpyStrategy = function() {
1577 |
1578 | function SpyStrategy(options) {
1579 | options = options || {};
1580 |
1581 | var identity = options.name || "unknown",
1582 | originalFn = options.fn || function() {},
1583 | getSpy = options.getSpy || function() {},
1584 | plan = function() {};
1585 |
1586 | this.identity = function() {
1587 | return identity;
1588 | };
1589 |
1590 | this.exec = function() {
1591 | return plan.apply(this, arguments);
1592 | };
1593 |
1594 | this.callThrough = function() {
1595 | plan = originalFn;
1596 | return getSpy();
1597 | };
1598 |
1599 | this.returnValue = function(value) {
1600 | plan = function() {
1601 | return value;
1602 | };
1603 | return getSpy();
1604 | };
1605 |
1606 | this.throwError = function(something) {
1607 | var error = (something instanceof Error) ? something : new Error(something);
1608 | plan = function() {
1609 | throw error;
1610 | };
1611 | return getSpy();
1612 | };
1613 |
1614 | this.callFake = function(fn) {
1615 | plan = fn;
1616 | return getSpy();
1617 | };
1618 |
1619 | this.stub = function(fn) {
1620 | plan = function() {};
1621 | return getSpy();
1622 | };
1623 | }
1624 |
1625 | return SpyStrategy;
1626 | };
1627 |
1628 | getJasmineRequireObj().Suite = function() {
1629 | function Suite(attrs) {
1630 | this.env = attrs.env;
1631 | this.id = attrs.id;
1632 | this.parentSuite = attrs.parentSuite;
1633 | this.description = attrs.description;
1634 | this.onStart = attrs.onStart || function() {};
1635 | this.resultCallback = attrs.resultCallback || function() {};
1636 | this.clearStack = attrs.clearStack || function(fn) {fn();};
1637 |
1638 | this.beforeFns = [];
1639 | this.afterFns = [];
1640 | this.queueRunner = attrs.queueRunner || function() {};
1641 | this.disabled = false;
1642 |
1643 | this.children = [];
1644 |
1645 | this.result = {
1646 | id: this.id,
1647 | status: this.disabled ? 'disabled' : '',
1648 | description: this.description,
1649 | fullName: this.getFullName()
1650 | };
1651 | }
1652 |
1653 | Suite.prototype.getFullName = function() {
1654 | var fullName = this.description;
1655 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
1656 | if (parentSuite.parentSuite) {
1657 | fullName = parentSuite.description + ' ' + fullName;
1658 | }
1659 | }
1660 | return fullName;
1661 | };
1662 |
1663 | Suite.prototype.disable = function() {
1664 | this.disabled = true;
1665 | };
1666 |
1667 | Suite.prototype.beforeEach = function(fn) {
1668 | this.beforeFns.unshift(fn);
1669 | };
1670 |
1671 | Suite.prototype.afterEach = function(fn) {
1672 | this.afterFns.unshift(fn);
1673 | };
1674 |
1675 | Suite.prototype.addChild = function(child) {
1676 | this.children.push(child);
1677 | };
1678 |
1679 | Suite.prototype.execute = function(onComplete) {
1680 | var self = this;
1681 | if (this.disabled) {
1682 | complete();
1683 | return;
1684 | }
1685 |
1686 | var allFns = [];
1687 |
1688 | for (var i = 0; i < this.children.length; i++) {
1689 | allFns.push(wrapChildAsAsync(this.children[i]));
1690 | }
1691 |
1692 | this.onStart(this);
1693 |
1694 | this.queueRunner({
1695 | fns: allFns,
1696 | onComplete: complete
1697 | });
1698 |
1699 | function complete() {
1700 | self.resultCallback(self.result);
1701 |
1702 | if (onComplete) {
1703 | onComplete();
1704 | }
1705 | }
1706 |
1707 | function wrapChildAsAsync(child) {
1708 | return function(done) { child.execute(done); };
1709 | }
1710 | };
1711 |
1712 | return Suite;
1713 | };
1714 |
1715 | if (typeof window == void 0 && typeof exports == "object") {
1716 | exports.Suite = jasmineRequire.Suite;
1717 | }
1718 |
1719 | getJasmineRequireObj().Timer = function() {
1720 | function Timer(options) {
1721 | options = options || {};
1722 |
1723 | var now = options.now || function() { return new Date().getTime(); },
1724 | startTime;
1725 |
1726 | this.start = function() {
1727 | startTime = now();
1728 | };
1729 |
1730 | this.elapsed = function() {
1731 | return now() - startTime;
1732 | };
1733 | }
1734 |
1735 | return Timer;
1736 | };
1737 |
1738 | getJasmineRequireObj().matchersUtil = function(j$) {
1739 | // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
1740 |
1741 | return {
1742 | equals: function(a, b, customTesters) {
1743 | customTesters = customTesters || [];
1744 |
1745 | return eq(a, b, [], [], customTesters);
1746 | },
1747 |
1748 | contains: function(haystack, needle, customTesters) {
1749 | customTesters = customTesters || [];
1750 |
1751 | if (Object.prototype.toString.apply(haystack) === "[object Array]") {
1752 | for (var i = 0; i < haystack.length; i++) {
1753 | if (eq(haystack[i], needle, [], [], customTesters)) {
1754 | return true;
1755 | }
1756 | }
1757 | return false;
1758 | }
1759 | return haystack.indexOf(needle) >= 0;
1760 | },
1761 |
1762 | buildFailureMessage: function() {
1763 | var args = Array.prototype.slice.call(arguments, 0),
1764 | matcherName = args[0],
1765 | isNot = args[1],
1766 | actual = args[2],
1767 | expected = args.slice(3),
1768 | englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1769 |
1770 | var message = "Expected " +
1771 | j$.pp(actual) +
1772 | (isNot ? " not " : " ") +
1773 | englishyPredicate;
1774 |
1775 | if (expected.length > 0) {
1776 | for (var i = 0; i < expected.length; i++) {
1777 | if (i > 0) {
1778 | message += ",";
1779 | }
1780 | message += " " + j$.pp(expected[i]);
1781 | }
1782 | }
1783 |
1784 | return message + ".";
1785 | }
1786 | };
1787 |
1788 | // Equality function lovingly adapted from isEqual in
1789 | // [Underscore](http://underscorejs.org)
1790 | function eq(a, b, aStack, bStack, customTesters) {
1791 | var result = true;
1792 |
1793 | for (var i = 0; i < customTesters.length; i++) {
1794 | var customTesterResult = customTesters[i](a, b);
1795 | if (!j$.util.isUndefined(customTesterResult)) {
1796 | return customTesterResult;
1797 | }
1798 | }
1799 |
1800 | if (a instanceof j$.Any) {
1801 | result = a.jasmineMatches(b);
1802 | if (result) {
1803 | return true;
1804 | }
1805 | }
1806 |
1807 | if (b instanceof j$.Any) {
1808 | result = b.jasmineMatches(a);
1809 | if (result) {
1810 | return true;
1811 | }
1812 | }
1813 |
1814 | if (b instanceof j$.ObjectContaining) {
1815 | result = b.jasmineMatches(a);
1816 | if (result) {
1817 | return true;
1818 | }
1819 | }
1820 |
1821 | if (a instanceof Error && b instanceof Error) {
1822 | return a.message == b.message;
1823 | }
1824 |
1825 | // Identical objects are equal. `0 === -0`, but they aren't identical.
1826 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
1827 | if (a === b) { return a !== 0 || 1 / a == 1 / b; }
1828 | // A strict comparison is necessary because `null == undefined`.
1829 | if (a === null || b === null) { return a === b; }
1830 | var className = Object.prototype.toString.call(a);
1831 | if (className != Object.prototype.toString.call(b)) { return false; }
1832 | switch (className) {
1833 | // Strings, numbers, dates, and booleans are compared by value.
1834 | case '[object String]':
1835 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
1836 | // equivalent to `new String("5")`.
1837 | return a == String(b);
1838 | case '[object Number]':
1839 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
1840 | // other numeric values.
1841 | return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
1842 | case '[object Date]':
1843 | case '[object Boolean]':
1844 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
1845 | // millisecond representations. Note that invalid dates with millisecond representations
1846 | // of `NaN` are not equivalent.
1847 | return +a == +b;
1848 | // RegExps are compared by their source patterns and flags.
1849 | case '[object RegExp]':
1850 | return a.source == b.source &&
1851 | a.global == b.global &&
1852 | a.multiline == b.multiline &&
1853 | a.ignoreCase == b.ignoreCase;
1854 | }
1855 | if (typeof a != 'object' || typeof b != 'object') { return false; }
1856 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
1857 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
1858 | var length = aStack.length;
1859 | while (length--) {
1860 | // Linear search. Performance is inversely proportional to the number of
1861 | // unique nested structures.
1862 | if (aStack[length] == a) { return bStack[length] == b; }
1863 | }
1864 | // Add the first object to the stack of traversed objects.
1865 | aStack.push(a);
1866 | bStack.push(b);
1867 | var size = 0;
1868 | // Recursively compare objects and arrays.
1869 | if (className == '[object Array]') {
1870 | // Compare array lengths to determine if a deep comparison is necessary.
1871 | size = a.length;
1872 | result = size == b.length;
1873 | if (result) {
1874 | // Deep compare the contents, ignoring non-numeric properties.
1875 | while (size--) {
1876 | if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; }
1877 | }
1878 | }
1879 | } else {
1880 | // Objects with different constructors are not equivalent, but `Object`s
1881 | // from different frames are.
1882 | var aCtor = a.constructor, bCtor = b.constructor;
1883 | if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
1884 | isFunction(bCtor) && (bCtor instanceof bCtor))) {
1885 | return false;
1886 | }
1887 | // Deep compare objects.
1888 | for (var key in a) {
1889 | if (has(a, key)) {
1890 | // Count the expected number of properties.
1891 | size++;
1892 | // Deep compare each member.
1893 | if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
1894 | }
1895 | }
1896 | // Ensure that both objects contain the same number of properties.
1897 | if (result) {
1898 | for (key in b) {
1899 | if (has(b, key) && !(size--)) { break; }
1900 | }
1901 | result = !size;
1902 | }
1903 | }
1904 | // Remove the first object from the stack of traversed objects.
1905 | aStack.pop();
1906 | bStack.pop();
1907 |
1908 | return result;
1909 |
1910 | function has(obj, key) {
1911 | return obj.hasOwnProperty(key);
1912 | }
1913 |
1914 | function isFunction(obj) {
1915 | return typeof obj === 'function';
1916 | }
1917 | }
1918 | };
1919 |
1920 | getJasmineRequireObj().toBe = function() {
1921 | function toBe() {
1922 | return {
1923 | compare: function(actual, expected) {
1924 | return {
1925 | pass: actual === expected
1926 | };
1927 | }
1928 | };
1929 | }
1930 |
1931 | return toBe;
1932 | };
1933 |
1934 | getJasmineRequireObj().toBeCloseTo = function() {
1935 |
1936 | function toBeCloseTo() {
1937 | return {
1938 | compare: function(actual, expected, precision) {
1939 | if (precision !== 0) {
1940 | precision = precision || 2;
1941 | }
1942 |
1943 | return {
1944 | pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
1945 | };
1946 | }
1947 | };
1948 | }
1949 |
1950 | return toBeCloseTo;
1951 | };
1952 |
1953 | getJasmineRequireObj().toBeDefined = function() {
1954 | function toBeDefined() {
1955 | return {
1956 | compare: function(actual) {
1957 | return {
1958 | pass: (void 0 !== actual)
1959 | };
1960 | }
1961 | };
1962 | }
1963 |
1964 | return toBeDefined;
1965 | };
1966 |
1967 | getJasmineRequireObj().toBeFalsy = function() {
1968 | function toBeFalsy() {
1969 | return {
1970 | compare: function(actual) {
1971 | return {
1972 | pass: !!!actual
1973 | };
1974 | }
1975 | };
1976 | }
1977 |
1978 | return toBeFalsy;
1979 | };
1980 |
1981 | getJasmineRequireObj().toBeGreaterThan = function() {
1982 |
1983 | function toBeGreaterThan() {
1984 | return {
1985 | compare: function(actual, expected) {
1986 | return {
1987 | pass: actual > expected
1988 | };
1989 | }
1990 | };
1991 | }
1992 |
1993 | return toBeGreaterThan;
1994 | };
1995 |
1996 |
1997 | getJasmineRequireObj().toBeLessThan = function() {
1998 | function toBeLessThan() {
1999 | return {
2000 |
2001 | compare: function(actual, expected) {
2002 | return {
2003 | pass: actual < expected
2004 | };
2005 | }
2006 | };
2007 | }
2008 |
2009 | return toBeLessThan;
2010 | };
2011 | getJasmineRequireObj().toBeNaN = function(j$) {
2012 |
2013 | function toBeNaN() {
2014 | return {
2015 | compare: function(actual) {
2016 | var result = {
2017 | pass: (actual !== actual)
2018 | };
2019 |
2020 | if (result.pass) {
2021 | result.message = "Expected actual not to be NaN.";
2022 | } else {
2023 | result.message = "Expected " + j$.pp(actual) + " to be NaN.";
2024 | }
2025 |
2026 | return result;
2027 | }
2028 | };
2029 | }
2030 |
2031 | return toBeNaN;
2032 | };
2033 |
2034 | getJasmineRequireObj().toBeNull = function() {
2035 |
2036 | function toBeNull() {
2037 | return {
2038 | compare: function(actual) {
2039 | return {
2040 | pass: actual === null
2041 | };
2042 | }
2043 | };
2044 | }
2045 |
2046 | return toBeNull;
2047 | };
2048 |
2049 | getJasmineRequireObj().toBeTruthy = function() {
2050 |
2051 | function toBeTruthy() {
2052 | return {
2053 | compare: function(actual) {
2054 | return {
2055 | pass: !!actual
2056 | };
2057 | }
2058 | };
2059 | }
2060 |
2061 | return toBeTruthy;
2062 | };
2063 |
2064 | getJasmineRequireObj().toBeUndefined = function() {
2065 |
2066 | function toBeUndefined() {
2067 | return {
2068 | compare: function(actual) {
2069 | return {
2070 | pass: void 0 === actual
2071 | };
2072 | }
2073 | };
2074 | }
2075 |
2076 | return toBeUndefined;
2077 | };
2078 |
2079 | getJasmineRequireObj().toContain = function() {
2080 | function toContain(util, customEqualityTesters) {
2081 | customEqualityTesters = customEqualityTesters || [];
2082 |
2083 | return {
2084 | compare: function(actual, expected) {
2085 |
2086 | return {
2087 | pass: util.contains(actual, expected, customEqualityTesters)
2088 | };
2089 | }
2090 | };
2091 | }
2092 |
2093 | return toContain;
2094 | };
2095 |
2096 | getJasmineRequireObj().toEqual = function() {
2097 |
2098 | function toEqual(util, customEqualityTesters) {
2099 | customEqualityTesters = customEqualityTesters || [];
2100 |
2101 | return {
2102 | compare: function(actual, expected) {
2103 | var result = {
2104 | pass: false
2105 | };
2106 |
2107 | result.pass = util.equals(actual, expected, customEqualityTesters);
2108 |
2109 | return result;
2110 | }
2111 | };
2112 | }
2113 |
2114 | return toEqual;
2115 | };
2116 |
2117 | getJasmineRequireObj().toHaveBeenCalled = function(j$) {
2118 |
2119 | function toHaveBeenCalled() {
2120 | return {
2121 | compare: function(actual) {
2122 | var result = {};
2123 |
2124 | if (!j$.isSpy(actual)) {
2125 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
2126 | }
2127 |
2128 | if (arguments.length > 1) {
2129 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
2130 | }
2131 |
2132 | result.pass = actual.calls.any();
2133 |
2134 | result.message = result.pass ?
2135 | "Expected spy " + actual.and.identity() + " not to have been called." :
2136 | "Expected spy " + actual.and.identity() + " to have been called.";
2137 |
2138 | return result;
2139 | }
2140 | };
2141 | }
2142 |
2143 | return toHaveBeenCalled;
2144 | };
2145 |
2146 | getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
2147 |
2148 | function toHaveBeenCalledWith(util) {
2149 | return {
2150 | compare: function() {
2151 | var args = Array.prototype.slice.call(arguments, 0),
2152 | actual = args[0],
2153 | expectedArgs = args.slice(1),
2154 | result = { pass: false };
2155 |
2156 | if (!j$.isSpy(actual)) {
2157 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
2158 | }
2159 |
2160 | if (!actual.calls.any()) {
2161 | result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but it was never called.";
2162 | return result;
2163 | }
2164 |
2165 | if (util.contains(actual.calls.allArgs(), expectedArgs)) {
2166 | result.pass = true;
2167 | result.message = "Expected spy " + actual.and.identity() + " not to have been called with " + j$.pp(expectedArgs) + " but it was.";
2168 | } else {
2169 | result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + ".";
2170 | }
2171 |
2172 | return result;
2173 | }
2174 | };
2175 | }
2176 |
2177 | return toHaveBeenCalledWith;
2178 | };
2179 |
2180 | getJasmineRequireObj().toMatch = function() {
2181 |
2182 | function toMatch() {
2183 | return {
2184 | compare: function(actual, expected) {
2185 | var regexp = new RegExp(expected);
2186 |
2187 | return {
2188 | pass: regexp.test(actual)
2189 | };
2190 | }
2191 | };
2192 | }
2193 |
2194 | return toMatch;
2195 | };
2196 |
2197 | getJasmineRequireObj().toThrow = function(j$) {
2198 |
2199 | function toThrow(util) {
2200 | return {
2201 | compare: function(actual, expected) {
2202 | var result = { pass: false },
2203 | threw = false,
2204 | thrown;
2205 |
2206 | if (typeof actual != "function") {
2207 | throw new Error("Actual is not a Function");
2208 | }
2209 |
2210 | try {
2211 | actual();
2212 | } catch (e) {
2213 | threw = true;
2214 | thrown = e;
2215 | }
2216 |
2217 | if (!threw) {
2218 | result.message = "Expected function to throw an exception.";
2219 | return result;
2220 | }
2221 |
2222 | if (arguments.length == 1) {
2223 | result.pass = true;
2224 | result.message = "Expected function not to throw, but it threw " + j$.pp(thrown) + ".";
2225 |
2226 | return result;
2227 | }
2228 |
2229 | if (util.equals(thrown, expected)) {
2230 | result.pass = true;
2231 | result.message = "Expected function not to throw " + j$.pp(expected) + ".";
2232 | } else {
2233 | result.message = "Expected function to throw " + j$.pp(expected) + ", but it threw " + j$.pp(thrown) + ".";
2234 | }
2235 |
2236 | return result;
2237 | }
2238 | };
2239 | }
2240 |
2241 | return toThrow;
2242 | };
2243 |
2244 | getJasmineRequireObj().toThrowError = function(j$) {
2245 | function toThrowError (util) {
2246 | return {
2247 | compare: function(actual) {
2248 | var threw = false,
2249 | thrown,
2250 | errorType,
2251 | message,
2252 | regexp,
2253 | name,
2254 | constructorName;
2255 |
2256 | if (typeof actual != "function") {
2257 | throw new Error("Actual is not a Function");
2258 | }
2259 |
2260 | extractExpectedParams.apply(null, arguments);
2261 |
2262 | try {
2263 | actual();
2264 | } catch (e) {
2265 | threw = true;
2266 | thrown = e;
2267 | }
2268 |
2269 | if (!threw) {
2270 | return fail("Expected function to throw an Error.");
2271 | }
2272 |
2273 | if (!(thrown instanceof Error)) {
2274 | return fail("Expected function to throw an Error, but it threw " + thrown + ".");
2275 | }
2276 |
2277 | if (arguments.length == 1) {
2278 | return pass("Expected function not to throw an Error, but it threw " + fnNameFor(thrown) + ".");
2279 | }
2280 |
2281 | if (errorType) {
2282 | name = fnNameFor(errorType);
2283 | constructorName = fnNameFor(thrown.constructor);
2284 | }
2285 |
2286 | if (errorType && message) {
2287 | if (thrown.constructor == errorType && util.equals(thrown.message, message)) {
2288 | return pass("Expected function not to throw " + name + " with message \"" + message + "\".");
2289 | } else {
2290 | return fail("Expected function to throw " + name + " with message \"" + message +
2291 | "\", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
2292 | }
2293 | }
2294 |
2295 | if (errorType && regexp) {
2296 | if (thrown.constructor == errorType && regexp.test(thrown.message)) {
2297 | return pass("Expected function not to throw " + name + " with message matching " + regexp + ".");
2298 | } else {
2299 | return fail("Expected function to throw " + name + " with message matching " + regexp +
2300 | ", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
2301 | }
2302 | }
2303 |
2304 | if (errorType) {
2305 | if (thrown.constructor == errorType) {
2306 | return pass("Expected function not to throw " + name + ".");
2307 | } else {
2308 | return fail("Expected function to throw " + name + ", but it threw " + constructorName + ".");
2309 | }
2310 | }
2311 |
2312 | if (message) {
2313 | if (thrown.message == message) {
2314 | return pass("Expected function not to throw an exception with message " + j$.pp(message) + ".");
2315 | } else {
2316 | return fail("Expected function to throw an exception with message " + j$.pp(message) +
2317 | ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
2318 | }
2319 | }
2320 |
2321 | if (regexp) {
2322 | if (regexp.test(thrown.message)) {
2323 | return pass("Expected function not to throw an exception with a message matching " + j$.pp(regexp) + ".");
2324 | } else {
2325 | return fail("Expected function to throw an exception with a message matching " + j$.pp(regexp) +
2326 | ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
2327 | }
2328 | }
2329 |
2330 | function fnNameFor(func) {
2331 | return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
2332 | }
2333 |
2334 | function pass(notMessage) {
2335 | return {
2336 | pass: true,
2337 | message: notMessage
2338 | };
2339 | }
2340 |
2341 | function fail(message) {
2342 | return {
2343 | pass: false,
2344 | message: message
2345 | };
2346 | }
2347 |
2348 | function extractExpectedParams() {
2349 | if (arguments.length == 1) {
2350 | return;
2351 | }
2352 |
2353 | if (arguments.length == 2) {
2354 | var expected = arguments[1];
2355 |
2356 | if (expected instanceof RegExp) {
2357 | regexp = expected;
2358 | } else if (typeof expected == "string") {
2359 | message = expected;
2360 | } else if (checkForAnErrorType(expected)) {
2361 | errorType = expected;
2362 | }
2363 |
2364 | if (!(errorType || message || regexp)) {
2365 | throw new Error("Expected is not an Error, string, or RegExp.");
2366 | }
2367 | } else {
2368 | if (checkForAnErrorType(arguments[1])) {
2369 | errorType = arguments[1];
2370 | } else {
2371 | throw new Error("Expected error type is not an Error.");
2372 | }
2373 |
2374 | if (arguments[2] instanceof RegExp) {
2375 | regexp = arguments[2];
2376 | } else if (typeof arguments[2] == "string") {
2377 | message = arguments[2];
2378 | } else {
2379 | throw new Error("Expected error message is not a string or RegExp.");
2380 | }
2381 | }
2382 | }
2383 |
2384 | function checkForAnErrorType(type) {
2385 | if (typeof type !== "function") {
2386 | return false;
2387 | }
2388 |
2389 | var Surrogate = function() {};
2390 | Surrogate.prototype = type.prototype;
2391 | return (new Surrogate()) instanceof Error;
2392 | }
2393 | }
2394 | };
2395 | }
2396 |
2397 | return toThrowError;
2398 | };
2399 |
2400 | getJasmineRequireObj().version = function() {
2401 | return "2.0.0";
2402 | };
2403 |
--------------------------------------------------------------------------------
/lib/run-jasmine.js:
--------------------------------------------------------------------------------
1 | // Usage: phantomjs run-jasmine.js ../sample.html
2 | // Generated by CoffeeScript 1.3.3
3 | (function() {
4 | var page, system, waitFor;
5 |
6 | system = require('system');
7 |
8 | waitFor = function(testFx, onReady, timeOutMillis) {
9 | var condition, f, interval, start;
10 | if (timeOutMillis == null) {
11 | timeOutMillis = 3000;
12 | }
13 | start = new Date().getTime();
14 | condition = false;
15 | f = function() {
16 | if ((new Date().getTime() - start < timeOutMillis) && !condition) {
17 | return condition = (typeof testFx === 'string' ? eval(testFx) : testFx());
18 | } else {
19 | if (!condition) {
20 | console.log("'waitFor()' timeout");
21 | return phantom.exit(1);
22 | } else {
23 | if (typeof onReady === 'string') {
24 | eval(onReady);
25 | } else {
26 | onReady();
27 | }
28 | return clearInterval(interval);
29 | }
30 | }
31 | };
32 | return interval = setInterval(f, 100);
33 | };
34 |
35 | if (system.args.length !== 2) {
36 | console.log('Usage: phantomjs run-jasmine.js URL');
37 | phantom.exit(1);
38 | }
39 |
40 | page = require('webpage').create();
41 |
42 | page.onConsoleMessage = function(msg) {
43 | return console.log(msg);
44 | };
45 |
46 | page.open(system.args[1], function(status) {
47 | if (status !== 'success') {
48 | console.log('Unable to access network');
49 | return phantom.exit(1);
50 | } else {
51 | page.evaluate(function() {
52 | var original;
53 | original = jasmine.ExpectationResult;
54 | jasmine.ExpectationResult = function(params) {
55 | original.apply(this, [params]);
56 | if (!this.passed_ && !(this.trace.stack != null)) {
57 | try {
58 | throw new Error(this.message);
59 | } catch (e) {
60 | this.trace = e;
61 | return null;
62 | }
63 | }
64 | };
65 | jasmine.ExpectationResult.prototype = original.prototype;
66 | window.COLOR_ES = {
67 | "black": "\x1b[30m",
68 | "red": "\x1b[31m",
69 | "green": "\x1b[32m",
70 | "yellow": "\x1b[33m",
71 | "blue": "\x1b[34m",
72 | "magenta": "\x1b[35m",
73 | "cyan": "\x1b[36m",
74 | "white": "\x1b[37m",
75 | "default": "\x1b[39m"
76 | };
77 | return window.logWithColor = function(log, color) {
78 | if (!COLOR_ES[color]) {
79 | console.log(log);
80 | return;
81 | }
82 | return console.log("" + COLOR_ES[color] + log + COLOR_ES['default']);
83 | };
84 | });
85 | return waitFor(function() {
86 | return page.evaluate(function() {
87 | return document.body.querySelector('.symbolSummary .pending') === null;
88 | });
89 | }, function() {
90 | var exitCode;
91 | exitCode = page.evaluate(function() {
92 | var el, failsCount, i, index, line, list, specsCount, stackTrace, _i, _j, _len, _len1, _ref;
93 | console.log(document.body.querySelector('.jasmine_reporter .duration').innerText);
94 | specsCount = document.body.querySelectorAll(".symbolSummary li").length;
95 | failsCount = document.body.querySelectorAll('.symbolSummary li.failed').length;
96 | if (failsCount === 0) {
97 | logWithColor("" + specsCount + " examples, 0 failures", 'green');
98 | return 0;
99 | }
100 | logWithColor("" + specsCount + " examples, " + failsCount + " failures", 'red');
101 | console.log("\nFailures:\n");
102 | list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
103 | for (i = _i = 0, _len = list.length; _i < _len; i = ++_i) {
104 | el = list[i];
105 | logWithColor(" " + (i + 1) + ") " + (el.querySelector('.description').innerText));
106 | logWithColor(" " + (el.querySelector('.resultMessage.fail').innerText), 'red');
107 | if (el.querySelector('.stackTrace')) {
108 | stackTrace = [];
109 | _ref = el.querySelector('.stackTrace').innerText.split(/\n/);
110 | for (index = _j = 0, _len1 = _ref.length; _j < _len1; index = ++_j) {
111 | line = _ref[index];
112 | if (index === 0) {
113 | continue;
114 | }
115 | if (line.indexOf("__JASMINE_ROOT__") !== -1) {
116 | continue;
117 | }
118 | if (line.indexOf("/lib/jasmine-1.2.0/jasmine.js") !== -1) {
119 | continue;
120 | }
121 | if (line.indexOf("phantomjs://webpage.evaluate") !== -1) {
122 | continue;
123 | }
124 | logWithColor(" " + line, 'cyan');
125 | }
126 | }
127 | console.log("");
128 | }
129 | return 1;
130 | });
131 | return phantom.exit(exitCode);
132 | });
133 | }
134 | });
135 |
136 | }).call(this);
137 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.narrows",
3 | "version": "0.3.1",
4 | "description": "jQuery Select Narrowing Plugin",
5 | "main": "jquery.narrows.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/monmonmon/jquery.narrows.git"
15 | },
16 | "keywords": [
17 | "jquery",
18 | "select",
19 | "hierselect"
20 | ],
21 | "author": "Shimon Yamada",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/monmonmon/jquery.narrows/issues"
25 | },
26 | "devDependencies": {
27 | "grunt": "0.4.1",
28 | "grunt-contrib-watch": "0.5.3",
29 | "grunt-contrib-uglify": "0.2.4",
30 | "grunt-contrib-jshint": "~0.6.4",
31 | "grunt-contrib-jasmine": "~0.5.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sample-jasmine-2.0.0.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jquery.narrows.js
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
41 |
42 |
43 |
44 | 例1
45 |
46 | 基本的な「親→子」の階層関係
47 |
53 |
65 |
66 | $("#ex1-food-category").narrows("#ex1-food");
67 |
68 |
69 |
70 | 例2
71 |
72 | 親selectが未選択の場合、子selectは全て選択可能
73 |
79 |
91 |
92 | $("#ex2-food-category").narrows("#ex2-food", {
93 | disable_if_parent_is_null: false
94 | });
95 |
96 |
97 |
98 | 例3
99 |
100 | 「親→子→孫」の3世代
101 |
110 |
143 |
191 |
192 | $("#ex3-continent").narrows("#ex3-country");
193 | $("#ex3-country").narrows("#ex3-city");
194 |
195 |
196 |
197 | 例4
198 |
199 | 「親→子1、子2」
1つの親selectの選択結果が複数の子selectの選択肢に同時に影響
200 |
206 |
218 |
232 |
233 | $("#ex4-cuisine").narrows("#ex4-food, #ex4-alcohol");
234 |
235 |
236 |
237 | 例5
238 |
239 | 「親1、親2→子」
複数の親selectの選択結果が1つの子selectの選択肢に影響。Country と Category 両方を選択して初めて Menu が選択可能になります
240 |
246 |
252 |
267 |
268 | $("#ex5-country, #ex5-category").narrows("#ex5-menu");
269 |
270 |
271 |
272 | 例6
273 |
274 |
275 | 子selectの1つのoptionが、親selectの複数のoptionにマッチ。
276 | うどんやカレーは食べ物ですが、飲み物であると主張する人々もいます。
277 |
278 |
283 |
291 |
292 | <select id="ex6-category">
293 | <option value="">-- Food Category --</option>
294 | <option value="food">食べ物</option>
295 | <option value="drink">飲み物</option>
296 | </select>
297 | <select id="ex6-menu">
298 | <option value="">-- Menu --</option>
299 | <option value="sushi" data-ex6-category="food">寿司</option>
300 | <option value="tempura" data-ex6-category="food">天ぷら</option>
301 | <option value="green-tea" data-ex6-category="drink">お茶</option>
302 | <option value="udon" data-ex6-category="food,drink">うどん</option><!-- data-ex6-category に複数のカテゴリを指定 -->
303 | <option value="dumpling" data-ex6-category="food,drink">カレー</option><!-- 同上 -->
304 | </select>
305 |
306 |
307 | $("#ex6-category").narrows("#ex6-menu", {
308 | allow_multiple_parent_values: true
309 | });
310 |
311 |
312 |
313 |
314 |
315 |
--------------------------------------------------------------------------------
/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jquery.narrows.js
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
42 |
43 |
44 |
45 | 例1
46 |
47 | 基本的な「親→子」の階層関係
48 |
54 |
66 |
67 | $("#ex1-food-category").narrows("#ex1-food");
68 |
69 |
70 |
71 | 例2
72 |
73 | 親selectが未選択の場合、子selectは全て選択可能
74 |
80 |
92 |
93 | $("#ex2-food-category").narrows("#ex2-food", {
94 | disable_if_parent_is_null: false
95 | });
96 |
97 |
98 |
99 | 例3
100 |
101 | 「親→子→孫」の3世代
102 |
111 |
144 |
192 |
193 | $("#ex3-continent").narrows("#ex3-country");
194 | $("#ex3-country").narrows("#ex3-city");
195 |
196 |
197 |
198 | 例4
199 |
200 | 「親→子1、子2」
1つの親selectの選択結果が複数の子selectの選択肢に同時に影響
201 |
207 |
219 |
233 |
234 | $("#ex4-cuisine").narrows("#ex4-food, #ex4-alcohol");
235 |
236 |
237 |
238 | 例5
239 |
240 | 「親1、親2→子」
複数の親selectの選択結果が1つの子selectの選択肢に影響。Country と Category 両方を選択して初めて Menu が選択可能になります
241 |
247 |
253 |
268 |
269 | $("#ex5-country, #ex5-category").narrows("#ex5-menu");
270 |
271 |
272 |
273 | 例6
274 |
275 |
276 | 子selectの1つのoptionが、親selectの複数のoptionにマッチ。
277 | うどんやカレーは食べ物ですが、飲み物であると主張する人々もいます。
278 |
279 |
284 |
292 |
293 | <select id="ex6-category">
294 | <option value="">-- Food Category --</option>
295 | <option value="food">食べ物</option>
296 | <option value="drink">飲み物</option>
297 | </select>
298 | <select id="ex6-menu">
299 | <option value="">-- Menu --</option>
300 | <option value="sushi" data-ex6-category="food">寿司</option>
301 | <option value="tempura" data-ex6-category="food">天ぷら</option>
302 | <option value="green-tea" data-ex6-category="drink">お茶</option>
303 | <option value="udon" data-ex6-category="food,drink">うどん</option><!-- data-ex6-category に複数のカテゴリを指定 -->
304 | <option value="dumpling" data-ex6-category="food,drink">カレー</option><!-- 同上 -->
305 | </select>
306 |
307 |
308 | $("#ex6-category").narrows("#ex6-menu", {
309 | allow_multiple_parent_values: true
310 | });
311 |
312 |
313 |
314 |
315 |
316 |
--------------------------------------------------------------------------------
/spec/narrowsSpec.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // 例1:親→子
3 | describe('例1', function() {
4 | // jquery instances of the select elements
5 | var $category = $('#ex1-food-category');
6 | var $food = $('#ex1-food');
7 |
8 | // 各テストの最初に選択状態を初期化
9 | beforeEach(function() {
10 | $category.val('').trigger('change');
11 | $food.val('').trigger('change');
12 | });
13 |
14 | it('food は初期状態で disabled', function () {
15 | expect($category.is(':disabled')).toBe(false);
16 | expect($food.is(':disabled')).toBe(true);
17 | });
18 |
19 | it('category で "meat" を選択したら food は肉に絞り込まれる', function () {
20 | // category を選択
21 | $category.val('meat').trigger('change');
22 | // food の disabled 解除
23 | expect($category.is(':disabled')).toBe(false);
24 | expect($food.is(':disabled')).toBe(false);
25 | // value="" を含め絞りこまれたoptionは2つ以上
26 | expect($food.find('option').size()).toBeGreaterThan(1);
27 | // option の data-xx 属性が全て "meat"
28 | $food.find('option').each(function () {
29 | if ($(this).val()) {
30 | expect($(this).data('ex1-food-category')).toEqual('meat');
31 | }
32 | });
33 | });
34 |
35 | it('category で value="" を選択したら food は disabled', function () {
36 | // category, food を選択してから
37 | $category.val('meat').trigger('change');
38 | $food.val('beef').trigger('change');
39 | // category を非選択状態にする
40 | $category.val('').trigger('change');
41 | // food は disabled
42 | expect($category.is(':disabled')).toBe(false);
43 | expect($food.is(':disabled')).toBe(true);
44 | // food は value=""
45 | expect($food.val()).toBe('');
46 | });
47 | });
48 |
49 | // 例2:親→子、親selectが未選択の場合、子selectは全て選択可能
50 | describe('例2', function() {
51 | // jquery instances of the select elements
52 | var $category = $('#ex2-food-category');
53 | var $food = $('#ex2-food');
54 |
55 | // 各テストの最初に選択状態を初期化
56 | beforeEach(function() {
57 | $category.val('').trigger('change');
58 | $food.val('').trigger('change');
59 | });
60 |
61 | it('category, food とも初期状態で enabled', function () {
62 | expect($category.is(':disabled')).toBe(false);
63 | expect($food.is(':disabled')).toBe(false);
64 | });
65 |
66 | it('category で "meat" を選択したら food は肉に絞り込まれる', function () {
67 | // category を選択
68 | $category.val('meat').trigger('change');
69 | // foodのdisabled解除
70 | expect($category.is(':disabled')).toBe(false);
71 | expect($food.is(':disabled')).toBe(false);
72 | // value="" を含め絞りこまれたoptionは2つ以上
73 | expect($food.find('option').size()).toBeGreaterThan(1);
74 | // option の data-xx 属性が全て "meat"
75 | $food.find('option').each(function () {
76 | if ($(this).val()) {
77 | expect($(this).data('ex2-food-category')).toEqual('meat');
78 | }
79 | });
80 | });
81 |
82 | it('category で value="" を選択したら、food は全ての option が選択可能になる', function () {
83 | // category, food を選択してから
84 | $category.val('meat').trigger('change');
85 | $food.val('beef').trigger('change');
86 | // category を非選択状態にする
87 | $category.val('').trigger('change');
88 | // food は enabled
89 | expect($category.is(':disabled')).toBe(false);
90 | expect($food.is(':disabled')).toBe(false);
91 | // food の選択肢は様々
92 | var categories = $food.find('option[value!=""]').map(function () {
93 | return $(this).data('ex2-food-category');
94 | }).get();
95 | expect(categories.indexOf('meat')).not.toBe(-1);
96 | expect(categories.indexOf('vegetable')).not.toBe(-1);
97 | expect(categories.indexOf('fruit')).not.toBe(-1);
98 | });
99 | });
100 |
101 | // 例3:親→子→孫
102 | describe('例3', function() {
103 | // jquery instances of the select elements
104 | var $continent = $('#ex3-continent');
105 | var $country = $('#ex3-country');
106 | var $city = $('#ex3-city');
107 |
108 | // 各テストの最初に選択状態を初期化
109 | beforeEach(function() {
110 | $continent.val('').trigger('change');
111 | $country.val('').trigger('change');
112 | $city.val('').trigger('change');
113 | });
114 |
115 | it('country, city は初期状態で disabled', function () {
116 | expect($continent.is(':disabled')).toBe(false);
117 | expect($country.is(':disabled')).toBe(true);
118 | expect($city.is(':disabled')).toBe(true);
119 | });
120 |
121 | it('continent で "asia" を選択したら country はアジアの国に絞り込まれる', function () {
122 | // continent を選択
123 | $continent.val('asia').trigger('change');
124 | // countryのdisabled解除、cityはdisabledのまま
125 | expect($continent.is(':disabled')).toBe(false);
126 | expect($country.is(':disabled')).toBe(false);
127 | expect($city.is(':disabled')).toBe(true);
128 | // value="" を含め絞りこまれたoptionは2つ以上
129 | expect($country.find('option').size()).toBeGreaterThan(1);
130 | // option の data-xx 属性が全て "asia"
131 | $country.find('option').each(function () {
132 | if ($(this).val()) {
133 | expect($(this).data('ex3-continent')).toEqual('asia');
134 | }
135 | });
136 | });
137 |
138 | it('continent で "asia", country で "japan" を選択したら city は日本の都市に絞り込まれる', function () {
139 | // continent, country を選択
140 | $continent.val('asia').trigger('change');
141 | $country.val('japan').trigger('change');
142 | // disabled全て解除
143 | expect($continent.is(':disabled')).toBe(false);
144 | expect($country.is(':disabled')).toBe(false);
145 | expect($city.is(':disabled')).toBe(false);
146 | // value="" を含め絞りこまれたoptionは2つ以上
147 | expect($city.find('option').size()).toBeGreaterThan(1);
148 | // option の data-xx 属性が全て "asia"
149 | $city.find('option').each(function () {
150 | if ($(this).val()) {
151 | expect($(this).data('ex3-country')).toEqual('japan');
152 | }
153 | });
154 | });
155 |
156 | it('continent で value="" を選択したら country, city は disabled', function () {
157 | // continent, country, city を選択してから
158 | $continent.val('asia').trigger('change');
159 | $country.val('japan').trigger('change');
160 | $city.val('tokyo').trigger('change');
161 | // continent を非選択状態にする
162 | $continent.val('').trigger('change');
163 | // country, city は disabled
164 | expect($continent.is(':disabled')).toBe(false);
165 | expect($country.is(':disabled')).toBe(true);
166 | expect($city.is(':disabled')).toBe(true);
167 | // country, city は value=""
168 | expect($country.val()).toBe('');
169 | expect($city.val()).toBe('');
170 | });
171 | });
172 |
173 | // 例4:親→子1、子2
174 | describe('例4', function() {
175 | // jquery instances of the select elements
176 | var $cuisine = $('#ex4-cuisine');
177 | var $food = $('#ex4-food');
178 | var $alcohol = $('#ex4-alcohol');
179 |
180 | // 各テストの最初に選択状態を初期化
181 | beforeEach(function() {
182 | $cuisine.val('').trigger('change');
183 | $food.val('').trigger('change');
184 | $alcohol.val('').trigger('change');
185 | });
186 |
187 | it('food, alcohol は初期状態で disabled', function () {
188 | expect($cuisine.is(':disabled')).toBe(false);
189 | expect($food.is(':disabled')).toBe(true);
190 | expect($alcohol.is(':disabled')).toBe(true);
191 | });
192 |
193 | it('cuisine で "japanese" を選択したら food, alcohol 両方とも絞り込まれる', function () {
194 | // cuisine で japanese を選択
195 | $cuisine.val('japanese').trigger('change');
196 | // food の disabled が解け、japanese で絞り込まれる
197 | expect($food.is(':disabled')).toBe(false);
198 | expect($food.find('option').size()).toBeGreaterThan(1);
199 | $food.find('option').each(function () {
200 | if ($(this).val()) {
201 | expect($(this).data('ex4-cuisine')).toEqual('japanese');
202 | }
203 | });
204 | // alcohol の disabled が解け、japanese で絞り込まれる
205 | expect($alcohol.is(':disabled')).toBe(false);
206 | expect($alcohol.find('option').size()).toBeGreaterThan(1);
207 | $alcohol.find('option').each(function () {
208 | if ($(this).val()) {
209 | expect($(this).data('ex4-cuisine')).toEqual('japanese');
210 | }
211 | });
212 | });
213 |
214 | it('cuisine で value="" を選択したら food, alcohol は disabled', function () {
215 | // select cuisine
216 | $cuisine.val('japanese').trigger('change');
217 | // ... and reset
218 | $cuisine.val('').trigger('change');
219 | expect($food.is(':disabled')).toBe(true);
220 | expect($alcohol.is(':disabled')).toBe(true);
221 | });
222 | });
223 |
224 | // 例5:親1、親2→子
225 | describe('例5', function() {
226 | // jquery instances of the select elements
227 | var $country = $('#ex5-country');
228 | var $category = $('#ex5-category');
229 | var $menu = $('#ex5-menu');
230 |
231 | // 各テストの最初に選択状態を初期化
232 | beforeEach(function() {
233 | $country.val('').trigger('change');
234 | $category.val('').trigger('change');
235 | $menu.val('').trigger('change');
236 | });
237 |
238 | it('menu は初期状態で disabled', function () {
239 | expect($country.is(':disabled')).toBe(false);
240 | expect($category.is(':disabled')).toBe(false);
241 | expect($menu.is(':disabled')).toBe(true);
242 | });
243 |
244 | it('country だけ選択しても menu は disabled のまま', function () {
245 | // select country
246 | $country.val('japanese').trigger('change');
247 | expect($menu.is(':disabled')).toBe(true);
248 | });
249 |
250 | it('category だけ選択しても menu は disabled のまま', function () {
251 | // select category
252 | $category.val('food').trigger('change');
253 | expect($menu.is(':disabled')).toBe(true);
254 | });
255 |
256 | it('country で "japanese", category で "food" を選択したら menu が絞り込まれる', function () {
257 | // select country & category
258 | $country.val('japanese').trigger('change');
259 | $category.val('food').trigger('change');
260 | expect($menu.find('option').size()).toBeGreaterThan(1);
261 | $menu.find('option').each(function () {
262 | if ($(this).val()) {
263 | expect($(this).data('ex5-country')).toEqual('japanese');
264 | expect($(this).data('ex5-category')).toEqual('food');
265 | }
266 | });
267 | });
268 |
269 | it('country で value="" を選択したら menu は disabled', function () {
270 | // 一度両方選択してから
271 | $country.val('japanese').trigger('change');
272 | $category.val('food').trigger('change');
273 | // country を非選択状態にする
274 | $country.val('').trigger('change');
275 | expect($menu.is(':disabled')).toBe(true);
276 | expect($menu.val()).toBe('');
277 | });
278 |
279 | it('category で value="" を選択したら menu は disabled', function () {
280 | // 一度両方選択してから
281 | $country.val('japanese').trigger('change');
282 | $category.val('food').trigger('change');
283 | // category を非選択状態にする
284 | $category.val('').trigger('change');
285 | expect($menu.is(':disabled')).toBe(true);
286 | expect($menu.val()).toBe('');
287 | });
288 | });
289 |
290 | // 例6:親→子
291 | describe('例6', function() {
292 | // jquery instances of the select elements
293 | var $category = $('#ex6-category');
294 | var $menu = $('#ex6-menu');
295 |
296 | // 各テストの最初に選択状態を初期化
297 | beforeEach(function() {
298 | $category.val('').trigger('change');
299 | $menu.val('').trigger('change');
300 | });
301 |
302 | it('menu は初期状態で disabled', function () {
303 | expect($category.is(':disabled')).toBe(false);
304 | expect($menu.is(':disabled')).toBe(true);
305 | });
306 |
307 | it('category で "food" を選択したら menu にうどん、カレーが含まれる', function () {
308 | // category で food を選択
309 | $category.val('food').trigger('change');
310 | // menu にうどんが含まれることを確認
311 | expect($menu.find('option[value=udon]').size()).toBe(1);
312 | });
313 |
314 | it('category で "drink" を選択しても menu にうどん、カレーが含まれる', function () {
315 | // category で drink を選択
316 | $category.val('drink').trigger('change');
317 | // menu にうどんが含まれることを確認
318 | expect($menu.find('option[value=udon]').size()).toBe(1);
319 | });
320 | });
321 |
322 | // (dummy spec) reset all selections after the tests
323 | describe('(dummy spec)', function() {
324 | it('reset all selects', function () {
325 | $('#ex1-food-category').val('').trigger('change');
326 | $('#ex1-food').val('').trigger('change');
327 | $('#ex2-food-category').val('').trigger('change');
328 | $('#ex2-food').val('').trigger('change');
329 | $('#ex3-continent').val('').trigger('change');
330 | $('#ex3-country').val('').trigger('change');
331 | $('#ex3-city').val('').trigger('change');
332 | $('#ex4-cuisine').val('').trigger('change');
333 | $('#ex4-food').val('').trigger('change');
334 | $('#ex4-alcohol').val('').trigger('change');
335 | $('#ex5-country').val('').trigger('change');
336 | $('#ex5-category').val('').trigger('change');
337 | $('#ex5-menu').val('').trigger('change');
338 | $('#ex6-category').val('').trigger('change');
339 | $('#ex6-menu').val('').trigger('change');
340 | });
341 | });
342 | });
343 |
--------------------------------------------------------------------------------