pax_global_header00006660000000000000000000000064122737521630014522gustar00rootroot0000000000000052 comment=d5eedf38db6e6b49fb646cd7443556790c67207d angular.js-1.2.11/000077500000000000000000000000001227375216300136505ustar00rootroot00000000000000angular.js-1.2.11/.bowerrc000066400000000000000000000000761227375216300153170ustar00rootroot00000000000000{ "directory": "bower_components", "json": "bower.json" } angular.js-1.2.11/.gitignore000066400000000000000000000004361227375216300156430ustar00rootroot00000000000000/build/ .DS_Store gen_docs.disable test.disable regression/temp*.html performance/temp*.html .idea/workspace.xml *~ *.swp angular.js.tmproj /node_modules/ /components/ /bower_components/ angular.xcodeproj .idea .agignore libpeerconnection.log npm-debug.log /tmp/ /scripts/bower/bower-* angular.js-1.2.11/.jscs.json000066400000000000000000000000431227375216300155600ustar00rootroot00000000000000{ "disallowKeywords": ["with"] } angular.js-1.2.11/.jscs.json.todo000066400000000000000000000020061227375216300165250ustar00rootroot00000000000000// This is an incomplete TODO list of checks we want to start enforcing // // The goal is to enable these checks one by one by moving them to .jscs.json along with commits // that correct the existing code base issues and make the new check pass. { "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], "disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], "disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], "requireRightStickedOperators": ["!"], "requireLeftStickedOperators": [","], "disallowImplicitTypeConversion": ["string"], "disallowMultipleLineBreaks": true, "disallowKeywordsOnNewLine": ["else"], "disallowTrailingWhitespace": true, "requireLineFeedAtFileEnd": true, "validateJSDoc": { "checkParamNames": true, "requireParamTypes": true } } angular.js-1.2.11/.travis.yml000066400000000000000000000011171227375216300157610ustar00rootroot00000000000000language: node_js node_js: - 0.10 env: matrix: - JOB=unit - JOB=e2e global: - SAUCE_USERNAME=angular-ci - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - LOGS_DIR=/tmp/angular-build/logs - BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready before_script: - mkdir -p $LOGS_DIR - ./lib/sauce/sauce_connect_setup.sh - npm install -g grunt-cli - grunt bower - grunt bower - grunt package-without-bower - ./scripts/travis/wait_for_browser_provider.sh script: - ./scripts/travis/build.sh after_script: - ./scripts/travis/print_logs.sh angular.js-1.2.11/CHANGELOG.md000066400000000000000000007356641227375216300155060ustar00rootroot00000000000000 # 1.2.11 cryptocurrency-hyperdeflation (2014-02-03) ## Bug Fixes - **$compile:** retain CSS classes added in cloneAttachFn on asynchronous directives ([5ed721b9](https://github.com/angular/angular.js/commit/5ed721b9b5e95ae08450e1ae9d5202e7f3f79295), [#5439](https://github.com/angular/angular.js/issues/5439), [#5617](https://github.com/angular/angular.js/issues/5617)) - **$http:** update httpBackend to use ActiveXObject on IE8 if necessary ([ef210e5e](https://github.com/angular/angular.js/commit/ef210e5e119db4f5bfc9d2428b19f9b335c4f976), [#5677](https://github.com/angular/angular.js/issues/5677), [#5679](https://github.com/angular/angular.js/issues/5679)) - **$q:** make $q.reject support `finally` and `catch` ([074b0675](https://github.com/angular/angular.js/commit/074b0675a1f97dce07f520f1ae6198ed3c604000), [#6048](https://github.com/angular/angular.js/issues/6048), [#6076](https://github.com/angular/angular.js/issues/6076)) - **filterFilter:** don't interpret dots in predicate object fields as paths ([339a1658](https://github.com/angular/angular.js/commit/339a1658cd9bfa5e322a01c45aa0a1df67e3a842), [#6005](https://github.com/angular/angular.js/issues/6005), [#6009](https://github.com/angular/angular.js/issues/6009)) - **mocks:** refactor currentSpec to work w/ Jasmine 2 ([95f0bf9b](https://github.com/angular/angular.js/commit/95f0bf9b526fda8964527c6d4aef1ad50a47f1f3), [#5662](https://github.com/angular/angular.js/issues/5662)) - **ngResource:** don't append number to '$' in url param value when encoding URI ([ce1f1f97](https://github.com/angular/angular.js/commit/ce1f1f97f0ebf77941b2bdaf5e8352d33786524d), [#6003](https://github.com/angular/angular.js/issues/6003), [#6004](https://github.com/angular/angular.js/issues/6004)) # 1.2.10 augmented-serendipity (2014-01-24) ## Bug Fixes - **$parse:** do not use locals to resolve object properties ([f09b6aa5](https://github.com/angular/angular.js/commit/f09b6aa5b58c090e3b8f8811fb7735e38d4b7623), [#5838](https://github.com/angular/angular.js/issues/5838), [#5862](https://github.com/angular/angular.js/issues/5862)) - **a:** don't call preventDefault on click when a SVGAElement has an xlink:href attribute ([e0209169](https://github.com/angular/angular.js/commit/e0209169bf1463465ad07484421620748a4d3908), [#5896](https://github.com/angular/angular.js/issues/5896), [#5897](https://github.com/angular/angular.js/issues/5897)) - **input:** use Chromium's email validation regexp ([79e519fe](https://github.com/angular/angular.js/commit/79e519fedaec54390a8bdacfb1926bfce57a1eb6), [#5899](https://github.com/angular/angular.js/issues/5899), [#5924](https://github.com/angular/angular.js/issues/5924)) - **ngRoute:** pipe preceding route param no longer masks ? or * operator ([fd6bac7d](https://github.com/angular/angular.js/commit/fd6bac7de56f728a89782dc80c78f7d5c21bbc65), [#5920](https://github.com/angular/angular.js/issues/5920)) ## Features - **$animate:** provide support for a close callback ([ca6b7d0f](https://github.com/angular/angular.js/commit/ca6b7d0fa2e355ebd764230260758cee9a4ebe1e), [#5685](https://github.com/angular/angular.js/issues/5685), [#5053](https://github.com/angular/angular.js/issues/5053), [#4993](https://github.com/angular/angular.js/issues/4993)) # 1.2.9 enchanted-articulacy (2014-01-15) ## Bug Fixes - **$animate:** - ensure the final closing timeout respects staggering animations ([ed53100a](https://github.com/angular/angular.js/commit/ed53100a0dbc9119d5dfc8b7248845d4f6989df2)) - prevent race conditions for class-based animations when animating on the same CSS class ([4aa9df7a](https://github.com/angular/angular.js/commit/4aa9df7a7ae533531dfae1e3eb9646245d6b5ff4), [#5588](https://github.com/angular/angular.js/issues/5588)) - correctly detect and handle CSS transition changes during class addition and removal ([7d5d62da](https://github.com/angular/angular.js/commit/7d5d62dafe11620082c79da35958f8014eeb008c)) - avoid accidentally matching substrings when resolving the presence of className tokens ([524650a4](https://github.com/angular/angular.js/commit/524650a40ed20f01571e5466475749874ee67288)) - **$http:** ensure default headers PUT and POST are different objects ([e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe), [#5742](https://github.com/angular/angular.js/issues/5742), [#5747](https://github.com/angular/angular.js/issues/5747), [#5764](https://github.com/angular/angular.js/issues/5764)) - **$rootScope:** prevent infinite $digest by checking if asyncQueue is empty when decrementing ttl ([2cd09c9f](https://github.com/angular/angular.js/commit/2cd09c9f0e7766bcd191662841b7b1ffc3b6dc3f), [#2622](https://github.com/angular/angular.js/issues/2622)) ## Features - **$animate:** - provide support for DOM callbacks ([dde1b294](https://github.com/angular/angular.js/commit/dde1b2949727c297e214c99960141bfad438d7a4)) - use requestAnimationFrame instead of a timeout to issue a reflow ([4ae3184c](https://github.com/angular/angular.js/commit/4ae3184c5915aac9aa00889aa2153c8e84c14966), [#4278](https://github.com/angular/angular.js/issues/4278), [#4225](https://github.com/angular/angular.js/issues/4225)) # 1.2.8 interdimensional-cartography (2014-01-10) ## Bug Fixes - **$http:** - return responseText on IE8 for requests with responseType set ([a9cccbe1](https://github.com/angular/angular.js/commit/a9cccbe14f1bd9048f5dab4443f58c804d4259a1), [#4464](https://github.com/angular/angular.js/issues/4464), [#4738](https://github.com/angular/angular.js/issues/4738), [#5636](https://github.com/angular/angular.js/issues/5636)) - Allow status code 0 from any protocol ([28fc80bb](https://github.com/angular/angular.js/commit/28fc80bba0107075ab371fd0a7634a38891626b2), [#1356](https://github.com/angular/angular.js/issues/1356), [#5547](https://github.com/angular/angular.js/issues/5547)) - cancelled JSONP requests will not print error in the console ([95e1b2d6](https://github.com/angular/angular.js/commit/95e1b2d6121b4e26cf87dcf6746a7b8cb4c25e7f), [#5615](https://github.com/angular/angular.js/issues/5615), [#5616](https://github.com/angular/angular.js/issues/5616)) - **$location:** return '/' for root path in hashbang mode ([63cd873f](https://github.com/angular/angular.js/commit/63cd873fef3207deef30c7a7ed66f4b8f647dc12), [#5650](https://github.com/angular/angular.js/issues/5650), [#5712](https://github.com/angular/angular.js/issues/5712)) - **$parse:** fix CSP nested property evaluation, and issue that prevented its tests from failing ([3b1a4fe0](https://github.com/angular/angular.js/commit/3b1a4fe0c83c7898ecd7261ab4213998ee7be0ec), [#5591](https://github.com/angular/angular.js/issues/5591), [#5592](https://github.com/angular/angular.js/issues/5592)) - **closure:** add Closure externs for angular.$q.Promise.finally ([caeb7402](https://github.com/angular/angular.js/commit/caeb7402651702cd13df2f1594e9827439a8b760), [#4757](https://github.com/angular/angular.js/issues/4757)) - **ngMock window.inject:** Remove Error 'stack' property changes ([7e916455](https://github.com/angular/angular.js/commit/7e916455b36dc9ca4d4afc1e44cade90006d00e3)) ## Features - **select:** allow multiline ng-options ([43a2f3d0](https://github.com/angular/angular.js/commit/43a2f3d0bf435e3626cd679caff4281cfb3415bd), [#5602](https://github.com/angular/angular.js/issues/5602)) # 1.2.7 emoji-clairvoyance (2014-01-03) ## Bug Fixes - **$animate:** - ensue class-based animations are always skipped before structural post-digest tasks are run ([bc492c0f](https://github.com/angular/angular.js/commit/bc492c0fc17257ddf2bc5964e205379aa766b3d8), [#5582](https://github.com/angular/angular.js/issues/5582)) - remove trailing `s` from computed transition duration styles ([50bf0296](https://github.com/angular/angular.js/commit/50bf029625d603fc652f0f413e709f43803743db)) - **$http:** ([3d38fff8](https://github.com/angular/angular.js/commit/3d38fff8b4ea2fd60fadef2028ea4dcddfccb1a4)) - use ActiveX XHR when making PATCH requests on IE8 ([6c17d02b](https://github.com/angular/angular.js/commit/6c17d02bc4cc02f478775d62e1f9f77da9da82ad), [#2518](https://github.com/angular/angular.js/issues/2518), [#5043](https://github.com/angular/angular.js/issues/5043)) - fix 'type mismatch' error on IE8 after each request ([fd9a03e1](https://github.com/angular/angular.js/commit/fd9a03e147aac7e952c6dda1f381fd4662276ba2)) - Ignore multiple calls to onreadystatechange with readyState=4 ([4f572366](https://github.com/angular/angular.js/commit/4f57236614415eea919221ea5f99c4d8689b3267), [#5426](https://github.com/angular/angular.js/issues/5426)) - **$injector:** remove the `INSTANTIATING` flag properly when done ([186a5912](https://github.com/angular/angular.js/commit/186a5912288acfff0ee59dae29af83c37c987921), [#4361](https://github.com/angular/angular.js/issues/4361), [#5577](https://github.com/angular/angular.js/issues/5577)) - **$location:** - remove base href domain if the URL begins with '//' ([760f2fb7](https://github.com/angular/angular.js/commit/760f2fb73178e56c37397b3c5876f7dac96f0455), [#5606](https://github.com/angular/angular.js/issues/5606)) - fix $location.path() behaviour when $locationChangeStart is triggered by the browser ([cf686285](https://github.com/angular/angular.js/commit/cf686285c22d528440e173fdb65ad1052d96df3c), [#4989](https://github.com/angular/angular.js/issues/4989), [#5089](https://github.com/angular/angular.js/issues/5089), [#5118](https://github.com/angular/angular.js/issues/5118), [#5580](https://github.com/angular/angular.js/issues/5580)) - re-assign history after BFCache back on Android browser ([bddd46c8](https://github.com/angular/angular.js/commit/bddd46c8ecf49cfe6c999cd6b4a69b7d7e1f9a33), [#5425](https://github.com/angular/angular.js/issues/5425)) - **$resource:** prevent URL template from collapsing into an empty string ([131e4014](https://github.com/angular/angular.js/commit/131e4014b831ac81b7979c4523da81ebc5861c70), [#5455](https://github.com/angular/angular.js/issues/5455), [#5493](https://github.com/angular/angular.js/issues/5493)) - **$sanitize:** consider the `size` attribute as a valid/allowed attribute ([056c8493](https://github.com/angular/angular.js/commit/056c8493521988dbb330c6636135b505737da918), [#5522](https://github.com/angular/angular.js/issues/5522)) - **Scope:** don't let watch deregistration mess up the dirty-checking digest loop ([884ef0db](https://github.com/angular/angular.js/commit/884ef0dbcdfe614cedc824d079361b53e675d033), [#5525](https://github.com/angular/angular.js/issues/5525)) - **input:** - use apply on the change event only when one isn't already in progress ([a80049fd](https://github.com/angular/angular.js/commit/a80049fd0ac858eeeb645a4209cb2a661d0b4c33), [#5293](https://github.com/angular/angular.js/issues/5293)) - prevent double $digest when using jQuery trigger. ([1147f219](https://github.com/angular/angular.js/commit/1147f21999edf9a434cd8d24865a6455e744d858), [#5293](https://github.com/angular/angular.js/issues/5293)) - **ngRepeat:** allow for more flexible coding style in ngRepeat expression ([c9705b75](https://github.com/angular/angular.js/commit/c9705b755645a4bfe066243f2ba15a733c3787e1), [#5537](https://github.com/angular/angular.js/issues/5537), [#5598](https://github.com/angular/angular.js/issues/5598)) - **ngRoute:** instantiate controller when template is empty ([498365f2](https://github.com/angular/angular.js/commit/498365f219f65d6c29bdf2f03610a4d3646009bb), [#5550](https://github.com/angular/angular.js/issues/5550)) - **ngShow/ngHide, ngIf:** functions with zero args should be truthy ([01c5be46](https://github.com/angular/angular.js/commit/01c5be4681e34cdc5f5c461b7a618fefe8038919), [#5414](https://github.com/angular/angular.js/issues/5414)) ## Performance Improvements - **Scope:** limit propagation of $broadcast to scopes that have listeners for the event ([80e7a455](https://github.com/angular/angular.js/commit/80e7a4558490f7ffd33d142844b9153a5ed00e86), [#5341](https://github.com/angular/angular.js/issues/5341), [#5371](https://github.com/angular/angular.js/issues/5371)) # 1.2.6 taco-salsafication (2013-12-19) ## Bug Fixes - **$animate:** use a scheduled timeout in favor of a fallback property to close transitions ([54637a33](https://github.com/angular/angular.js/commit/54637a335f885110efaa702a3bab29c77644b36c), [#5255](https://github.com/angular/angular.js/issues/5255), [#5241](https://github.com/angular/angular.js/issues/5241), [#5405](https://github.com/angular/angular.js/issues/5405)) - **$compile:** remove invalid IE exceptional case for `href` ([c7a1d1ab](https://github.com/angular/angular.js/commit/c7a1d1ab0b663edffc1ac7b54deea847e372468d), [#5479](https://github.com/angular/angular.js/issues/5479)) - **$location:** parse xlink:href for SVGAElements ([bc3ff2ce](https://github.com/angular/angular.js/commit/bc3ff2cecd0861766a9e8606f3cc2c582d9875df), [#5472](https://github.com/angular/angular.js/issues/5472), [#5198](https://github.com/angular/angular.js/issues/5198), [#5199](https://github.com/angular/angular.js/issues/5199), [#4098](https://github.com/angular/angular.js/issues/4098), [#1420](https://github.com/angular/angular.js/issues/1420)) - **$log:** should work in IE8 ([4f5758e6](https://github.com/angular/angular.js/commit/4f5758e6669222369889c9e789601d25ff885530), [#5400](https://github.com/angular/angular.js/issues/5400)) - **$parse:** return `undefined` if an intermetiate property's value is `null` ([26d43cac](https://github.com/angular/angular.js/commit/26d43cacdc106765bd928d41600352198f887aef), [#5480](https://github.com/angular/angular.js/issues/5480)) - **closure:** add type definition for `Scope#$watchCollection` ([8f329ffb](https://github.com/angular/angular.js/commit/8f329ffb829410e1fd8f86a766929134e736e3e5), [#5475](https://github.com/angular/angular.js/issues/5475)) - **forEach:** allow looping over result of `querySelectorAll` in IE8 ([274a6734](https://github.com/angular/angular.js/commit/274a6734ef1fff543cc50388a0958d1988baeb57)) - **input:** do not hold input for composition on Android ([3dc18037](https://github.com/angular/angular.js/commit/3dc18037e8db8766641a4d39f0fee96077db1fcb), [#5308](https://github.com/angular/angular.js/issues/5308)) - **jqLite:** support unbind self within handler ([2f91cfd0](https://github.com/angular/angular.js/commit/2f91cfd0d2986899c38641100c1851b2f9d3888a)) - **ngRepeat:** allow multiline expressions ([cbb3ce2c](https://github.com/angular/angular.js/commit/cbb3ce2c309052b951d0cc87e4c6daa9c48a3dd8), [#5000](https://github.com/angular/angular.js/issues/5000)) - **select:** invalidate when `multiple`, `required`, and model is `[]` ([5c97731a](https://github.com/angular/angular.js/commit/5c97731a22ed87d64712e673efea0e8a05eae65f), [#5337](https://github.com/angular/angular.js/issues/5337)) ## Features - **jqLite:** provide support for `element.one()` ([937caab6](https://github.com/angular/angular.js/commit/937caab6475e53a7ea0206e992f8a52449232e78)) - **ngAnimate:** provide configuration support to match specific className values to trigger animations ([cef084ad](https://github.com/angular/angular.js/commit/cef084ade9072090259d8c679751cac3ffeaed51), [#5357](https://github.com/angular/angular.js/issues/5357), [#5283](https://github.com/angular/angular.js/issues/5283)) ## Performance Improvements - **compile:** add class 'ng-scope' before cloning and other micro-optimizations ([f3a796e5](https://github.com/angular/angular.js/commit/f3a796e522afdbd3b640d14426edb2fbfab463c5), [#5471](https://github.com/angular/angular.js/issues/5471)) - **$parse:** use a faster path when the number of path parts is low ([f4462319](https://github.com/angular/angular.js/commit/864b2596b246470cca9d4e223eaed720f4462319)) - use faster check for `$$` prefix ([06c5cfc7](https://github.com/angular/angular.js/commit/cb29632a5802e930262919b3db64ca4806c5cfc7)) # 1.2.5 singularity-expansion (2013-12-13) ## Bug Fixes - **$compile:** allow literals in isolate scope references ([43072e38](https://github.com/angular/angular.js/commit/43072e3812e32b89b97ad03144577cba50d4b776), [#5296](https://github.com/angular/angular.js/issues/5296)) - **angular-mocks:** use copy of mock data in $httpBackend ([f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe)) - **closure:** add missing FormController extern definitions ([1d5e18b0](https://github.com/angular/angular.js/commit/1d5e18b062c3e33b2a8d96aa58d905ed2cd48649), [#5303](https://github.com/angular/angular.js/issues/5303)) - **ngInclude:** add template to DOM before linking other directives ([30a8b7d0](https://github.com/angular/angular.js/commit/30a8b7d0b5d4882c2bf3b20eb696a02f5b667726), [#5247](https://github.com/angular/angular.js/issues/5247)) - **ngView:** add template to DOM before linking other directives ([f8944efe](https://github.com/angular/angular.js/commit/f8944efe70b81e02704df9b53ea2546c80c73d3b)) ## Performance Improvements - **$injector:** remove invoke optimization that doesn't work ([05e4fd34](https://github.com/angular/angular.js/commit/05e4fd3488b89e670c36869f18defe26deac2efa), [#5388](https://github.com/angular/angular.js/issues/5388)) - **$resource:** use shallow copy instead of angular.copy ([fcd2a813](https://github.com/angular/angular.js/commit/fcd2a8131a3cb3e59a616bf31e61510b5c3a97d3), [#5300](https://github.com/angular/angular.js/issues/5300)) - **a:** do not link when href or name exists in template ([f3de5b6e](https://github.com/angular/angular.js/commit/f3de5b6eac90baf649506072162f36dbc6d2f028), [#5362](https://github.com/angular/angular.js/issues/5362)) - **jqLite:** implement and use the `empty` method in place of `html(‘’)` ([3410f65e](https://github.com/angular/angular.js/commit/3410f65e790a81d457b4f4601a1e760a6f8ede5e), [#4457](https://github.com/angular/angular.js/issues/4457)) ## Breaking Changes - **angular-mocks:** due to [f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe), some tests that rely on identity comparison rather than equality comparison in checking mock http responses will be broken, since now each mock response is a copy of the original response. This is usually fixable by changing a `.toBe()` comparison to `toEqual()` inside of tests. # 1.2.4 wormhole-blaster (2013-12-06) ## Bug Fixes - **$animate:** - ensure animations work with directives that share a transclusion ([958d3d56](https://github.com/angular/angular.js/commit/958d3d56b1899a2cfc7b18c0292e5a1d8c64d0a5), [#4716](https://github.com/angular/angular.js/issues/4716), [#4871](https://github.com/angular/angular.js/issues/4871), [#5021](https://github.com/angular/angular.js/issues/5021), [#5278](https://github.com/angular/angular.js/issues/5278)) - ensure ms durations are properly rounded ([93901bdd](https://github.com/angular/angular.js/commit/93901bdde4bb9f0ba114ebb33b8885808e1823e1), [#5113](https://github.com/angular/angular.js/issues/5113), [#5162](https://github.com/angular/angular.js/issues/5162)) - **$compile:** - update cloned elements if the template arrives after the cloning ([b0972a2e](https://github.com/angular/angular.js/commit/b0972a2e75909e41dbac6e4413ada7df2d51df3a)) - ensure the isolated local watch `lastValue` is always in sync ([2d0f6ccb](https://github.com/angular/angular.js/commit/2d0f6ccba896fe34141d6d4f59eef6fba580c5c2), [#5182](https://github.com/angular/angular.js/issues/5182)) - **$rootScope:** - ensure that when the $destroy event is broadcast on $rootScope that it does something ([d802ed1b](https://github.com/angular/angular.js/commit/d802ed1b3680cfc1751777fac465b92ee29944dc), [#5169](https://github.com/angular/angular.js/issues/5169)) - ensure the phase is cleared within a digest if an exception is raised by a watcher ([d3c486dd](https://github.com/angular/angular.js/commit/d3c486dd6dfa8d5dca32a3e28aa685fb7260c878)) - **$sanitize:** don't rely on YARR regex engine executing immediately in order to prevent object mutation ([81b81856](https://github.com/angular/angular.js/commit/81b81856ee43d2876927c4e1f774affa87e99707), [#5193](https://github.com/angular/angular.js/issues/5193), [#5192](https://github.com/angular/angular.js/issues/5192)) - **closure:** closure compiler shouldn't rename .defaults.transformRequest ([f01087f8](https://github.com/angular/angular.js/commit/f01087f802839637843115cbcf99702e09d866f6)) - **input:** ensure ngModelWatch() triggers second digest pass when appropriate ([b6d54393](https://github.com/angular/angular.js/commit/b6d5439343b9801f7f2a009d0de09cba9aa21a1d), [#5258](https://github.com/angular/angular.js/issues/5258), [#5282](https://github.com/angular/angular.js/issues/5282)) - **isElement:** return boolean value rather than `truthy` value. ([2dbb6f9a](https://github.com/angular/angular.js/commit/2dbb6f9a54eb5ff5847eed11c85ac4cf119eb41c), [#4519](https://github.com/angular/angular.js/issues/4519), [#4534](https://github.com/angular/angular.js/issues/4534)) - **jqLite:** ignore incompatible nodes on find() ([1169b544](https://github.com/angular/angular.js/commit/1169b5445691e1495354d235a3badf05240e3904), [#4120](https://github.com/angular/angular.js/issues/4120)) - **ngInit:** evaluate ngInit before ngInclude ([0e50810c](https://github.com/angular/angular.js/commit/0e50810c53428f4c1f5bfdba9599df54cb7a6c6e), [#5167](https://github.com/angular/angular.js/issues/5167), [#5208](https://github.com/angular/angular.js/issues/5208)) - **ngSanitize:** prefer textContent to innerText to avoid layout trashing ([bf1972dc](https://github.com/angular/angular.js/commit/bf1972dc1e8ffbeaddfa53df1d49bc5a2177f09c)) ## Performance Improvements - **$parse:** micro-optimization for ensureSafeObject function ([689dfb16](https://github.com/angular/angular.js/commit/689dfb167924a61aef444ce7587fb987d8080990), [#5246](https://github.com/angular/angular.js/issues/5246)) - **Scope:** short-circuit after dirty-checking last dirty watcher ([d070450c](https://github.com/angular/angular.js/commit/d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd), [#5272](https://github.com/angular/angular.js/issues/5272), [#5287](https://github.com/angular/angular.js/issues/5287)) # 1.2.3 unicorn-zapper (2013-11-27) ## Bug Fixes - **$animate:** - ensure blocked keyframe animations are unblocked before the DOM operation ([2efe8230](https://github.com/angular/angular.js/commit/2efe82309ac8ff4f67df8b6e40a539ea31e15804), [#5106](https://github.com/angular/angular.js/issues/5106)) - ensure animations are disabled during bootstrap to prevent unwanted structural animations ([eed23332](https://github.com/angular/angular.js/commit/eed2333298412fbad04eda97ded3487c845b9eb9), [#5130](https://github.com/angular/angular.js/issues/5130)) - **$sanitize:** use the same whitelist mechanism as `$compile` does ([33352348](https://github.com/angular/angular.js/commit/333523483f3ce6dd3177b697a5e5a7177ca364c8), [#3748](https://github.com/angular/angular.js/issues/3748)) - **input:** react to form auto completion, through the `change` event, on modern browsers ([a090400f](https://github.com/angular/angular.js/commit/a090400f09d7993d102f527609879cdc74abae60), [#1460](https://github.com/angular/angular.js/issues/1460)) - **$attrs:** add `$attrs.$attr` to externs so that it isn't renamed on js minification ([bcca8054](https://github.com/angular/angular.js/commit/bcca80548dde85ffe3838c943ba8e5c2deb1c721)) ## Features No new features in this release ## Breaking Changes There are no breaking changes in this release (promise!) # 1.2.2 consciousness-inertia (2013-11-22) ## Bug Fixes - **$animate:** - ensure keyframe animations are blocked around the reflow ([6760d7a3](https://github.com/angular/angular.js/commit/6760d7a315d7ea5cbd4f8ab74b200f754a2041f4), [#5018](https://github.com/angular/angular.js/issues/5018)) - ensure transition animations are unblocked before the dom operation occurs ([062fbed8](https://github.com/angular/angular.js/commit/062fbed8fc3f7bc55433f8c6915c27520e6f63c5), [#5014](https://github.com/angular/angular.js/issues/5014), [#4265](https://github.com/angular/angular.js/issues/4265)) - ensure addClass/removeClass animations do not snap during reflow ([76e4db6f](https://github.com/angular/angular.js/commit/76e4db6f3d15199ac1fbe85f9cfa6079a1c4fa56), [#4892](https://github.com/angular/angular.js/issues/4892)) - ensure the DOM operation isn't run twice ([7067a8fb](https://github.com/angular/angular.js/commit/7067a8fb0b18d5b5489006e1960cee721a88b4d2), [#4949](https://github.com/angular/angular.js/issues/4949)) - **$compile:** - secure form[action] & iframe[srcdoc] ([0421cb42](https://github.com/angular/angular.js/commit/0421cb4200e672818ed10996e92311404c150c3a), [#4927](https://github.com/angular/angular.js/issues/4927), [#4933](https://github.com/angular/angular.js/issues/4933)) - ensure CSS classes are added and removed only when necessary ([0cd7e8f2](https://github.com/angular/angular.js/commit/0cd7e8f22721f62b62440bb059ae764ebbe7b42a)) - **$httpBackend:** only IE8 and below can't use `script.onload` for JSONP ([a3172a28](https://github.com/angular/angular.js/commit/a3172a285fd74b5aa6c8d68a4988c767c06f549c), [#4523](https://github.com/angular/angular.js/issues/4523), [#4527](https://github.com/angular/angular.js/issues/4527), [#4922](https://github.com/angular/angular.js/issues/4922)) - **$parse:** allow for new lines in expr when promise unwrapping is on ([40647b17](https://github.com/angular/angular.js/commit/40647b179c473f3f470bb1b3237d6f006269582f), [#4718](https://github.com/angular/angular.js/issues/4718)) - **$resource:** Always return a resource instance when calling class methods on resources. ([f6ecf9a3](https://github.com/angular/angular.js/commit/f6ecf9a3c9090593faf5fa50586c99a56b51c776), [#4545](https://github.com/angular/angular.js/issues/4545), [#5061](https://github.com/angular/angular.js/issues/5061)) - **httpBackend:** should not read response data when request is aborted ([6f1050df](https://github.com/angular/angular.js/commit/6f1050df4fa885bd59ce85adbef7350ea93911a3), [#4913](https://github.com/angular/angular.js/issues/4913), [#4940](https://github.com/angular/angular.js/issues/4940)) - **loader:** expose `$$minErr` to modules such as`ngResource` ([9e89a31b](https://github.com/angular/angular.js/commit/9e89a31b129e40c805178535c244899ffafb77d8), [#5050](https://github.com/angular/angular.js/issues/5050)) - **ngAnimate:** - correctly retain and restore existing styles during and after animation ([c42d0a04](https://github.com/angular/angular.js/commit/c42d0a041890b39fc98afd357ec1307a3a36208d), [#4869](https://github.com/angular/angular.js/issues/4869)) - use a fallback CSS property that doesn't break existing styles ([1d50663b](https://github.com/angular/angular.js/commit/1d50663b38ba042e8d748ffa6d48cfb5e93cfd7e), [#4902](https://github.com/angular/angular.js/issues/4902), [#5030](https://github.com/angular/angular.js/issues/5030)) - **ngClass:** ensure that ngClass only adds/removes the changed classes ([6b8bbe4d](https://github.com/angular/angular.js/commit/6b8bbe4d90640542eed5607a8c91f6b977b1d6c0), [#4960](https://github.com/angular/angular.js/issues/4960), [#4944](https://github.com/angular/angular.js/issues/4944)) - **ngController:** fix issue with ngInclude on the same element ([6288cf5c](https://github.com/angular/angular.js/commit/6288cf5ca471b0615a026fdb4db3ba242c9d8f88), [#4431](https://github.com/angular/angular.js/issues/4431)) - **ngInclude:** - Don't throw when the ngInclude element contains content with directives. ([0a7cbb33](https://github.com/angular/angular.js/commit/0a7cbb33b06778833a4d99b1868cc07690a827a7)) - allow ngInclude to load scripts when jQuery is included ([c47abd0d](https://github.com/angular/angular.js/commit/c47abd0dd7490576f4b84ee51ebaca385c1036da), [#3756](https://github.com/angular/angular.js/issues/3756)) - **ngMock:** fixes httpBackend expectation with body object ([4d16472b](https://github.com/angular/angular.js/commit/4d16472b918a3482942d76f1e273a5aa01f65e83), [#4956](https://github.com/angular/angular.js/issues/4956)) - **ngView:** Don't throw when the ngView element contains content with directives. ([e6521e74](https://github.com/angular/angular.js/commit/e6521e7491242504250b57dd0ee66af49e653c33), [#5069](https://github.com/angular/angular.js/issues/5069)) - **tests:** Correct tests for IE11 ([57924234](https://github.com/angular/angular.js/commit/579242346c4202ea58fc2cae6df232289cbea0bb), [#5046](https://github.com/angular/angular.js/issues/5046)) - **input:** hold listener during text composition ([a4e6d962](https://github.com/angular/angular.js/commit/a4e6d962d78b26f5112d48c4f88c1e6234d0cae7), [#4684](https://github.com/angular/angular.js/issues/4684)) # 1.2.1 underscore-empathy (2013-11-14) ## Bug Fixes - **$compile:** - accessing controllers of transcluded directives from children ([90f87072](https://github.com/angular/angular.js/commit/90f87072e83234ae366cfeb3c281503c31dad738), [#4935](https://github.com/angular/angular.js/issues/4935)) - correctly handle interpolated style in replace templates ([e1254b26](https://github.com/angular/angular.js/commit/e1254b266dfa2d4e3756e4317152dbdbcabe44be), [#4882](https://github.com/angular/angular.js/issues/4882)) - **$resource:** don't use $parse for @dotted.member ([9577702e](https://github.com/angular/angular.js/commit/9577702e8d2519c1a60f5ac4058e63bd7b919815)) - **bootstrap:** make IE8 happy ([a61b65d0](https://github.com/angular/angular.js/commit/a61b65d01b468502fe53d68818949d3fcc9f20f6)) - **loader:** don't rely on internal APIs ([8425e9fe](https://github.com/angular/angular.js/commit/8425e9fe383c17f6a5589c778658c5fc0570ae8f), [#4437](https://github.com/angular/angular.js/issues/4437), [#4874](https://github.com/angular/angular.js/issues/4874)) - **minErr:** remove references to internal APIs ([94764ee0](https://github.com/angular/angular.js/commit/94764ee08910726db1db7a1101c3001500306dea)) - **ngIf:** don't create multiple elements when changing from a truthy value to another thruthy value ([4612705e](https://github.com/angular/angular.js/commit/4612705ec297bc6ba714cb7a98f1be6aff77c4b8), [#4852](https://github.com/angular/angular.js/issues/4852)) - **urlUtils:** - make removal of windows drive from path safer ([89f435de](https://github.com/angular/angular.js/commit/89f435de847635e3ec339726e6f83cf3f0ee9091), [#4939](https://github.com/angular/angular.js/issues/4939)) - return right path for file:// on windows ([f925e8ca](https://github.com/angular/angular.js/commit/f925e8caa6c51a7d45ca9ead30601ec2e9d4464c), [#4680](https://github.com/angular/angular.js/issues/4680)) ## Features - **$parse:** revert hiding "private" properties ([4ab16aaa](https://github.com/angular/angular.js/commit/4ab16aaaf762e9038803da1f967ac8cb6650727d), [#4926](https://github.com/angular/angular.js/issues/4926), [#4842](https://github.com/angular/angular.js/issues/4842), [#4865](https://github.com/angular/angular.js/issues/4865), [#4859](https://github.com/angular/angular.js/issues/4859), [#4849](https://github.com/angular/angular.js/issues/4849)) # 1.2.0 timely-delivery (2013-11-08) ## Features - **animations:** - ensure CSS transitions can work with inherited CSS class definitions ([9d69a0a7](https://github.com/angular/angular.js/commit/9d69a0a7c75c937c0a49bb705d31252326b052df)) - provide support for staggering animations with CSS ([74848307](https://github.com/angular/angular.js/commit/74848307443c00ab07552336c56ddfa1e9ef6eff)) - **$parse:** secure expressions by hiding "private" properties ([3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e)) - **docs:** - provide index pages for each angular module ([a7e12b79](https://github.com/angular/angular.js/commit/a7e12b7959212f2fa88fe17d5a045cc9d8b22922)) - add forward slash shortcut key for search bar ([74912802](https://github.com/angular/angular.js/commit/74912802c644ca929e39a7583cb7a9a05f12e91f)) - **jqLite:** expose isolateScope() getter similar to scope() ([27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b)) - **misc:** add externs file for Closure Compiler ([9d0a6977](https://github.com/angular/angular.js/commit/9d0a69772c39bfc751ca2000c3b4b3381e51fe93)) ## Bug Fixes - **$animate:** - don't force animations to be enabled ([98adc9e0](https://github.com/angular/angular.js/commit/98adc9e0383dc05efad168f30a0725cb67f5eda8)) - only apply the fallback property if any transition animations are detected ([94700807](https://github.com/angular/angular.js/commit/9470080762aecca5285d0f5cac4ae01540bbad4c)) - avoid hanging animations if the active CSS transition class is missing ([b89584db](https://github.com/angular/angular.js/commit/b89584db10b63f346cbfd03f67fb92504e5bf362), [#4732](https://github.com/angular/angular.js/issues/4732), [#4490](https://github.com/angular/angular.js/issues/4490)) - ensure staggering animations understand multiple delay values ([41a2d5b3](https://github.com/angular/angular.js/commit/41a2d5b30f4feb90651eb577cf44852a6d2be72c)) - ensure the active class is not applied if cancelled during reflow ([e53ff431](https://github.com/angular/angular.js/commit/e53ff431e1472c0b2d5405d267d4e403ca31087e), [#4699](https://github.com/angular/angular.js/issues/4699)) - use direct DOM comparison when checking for $rootElement ([d434eabe](https://github.com/angular/angular.js/commit/d434eabec3955f8d56c859c93befe711bfa1de27), [#4679](https://github.com/angular/angular.js/issues/4679)) - ensure former nodes are fully cleaned up when a follow-up structural animation takes place ([7f0767ac](https://github.com/angular/angular.js/commit/7f0767acaba1ec3c8849244a604b0d1c8c376446), [#4435](https://github.com/angular/angular.js/issues/4435)) - ensure enable/disable animations work when the document node is used ([6818542c](https://github.com/angular/angular.js/commit/6818542c694aec6c811fb2fe2f86f7d16544c39b), [#4669](https://github.com/angular/angular.js/issues/4669)) - skip unnecessary addClass/removeClass animations ([76b628bc](https://github.com/angular/angular.js/commit/76b628bcb3511210d312ed667e5c14d908a9fed1), [#4401](https://github.com/angular/angular.js/issues/4401), [#2332](https://github.com/angular/angular.js/issues/2332)) - ensure animations work properly when the $rootElement is being animated ([2623de14](https://github.com/angular/angular.js/commit/2623de1426219dc799f63a3d155911f93fc03461), [#4397](https://github.com/angular/angular.js/issues/4397), [#4231](https://github.com/angular/angular.js/issues/4231)) - only cancel class-based animations if the follow-up class contains CSS transition/keyframe animation code ([f5289fe8](https://github.com/angular/angular.js/commit/f5289fe84ffc1f2368dae7bd14c420abbe76749e), [#4463](https://github.com/angular/angular.js/issues/4463), [#3784](https://github.com/angular/angular.js/issues/3784)) - **$compile:** - don't leak isolate scope state when replaced directive is used multiple times ([b5af198f](https://github.com/angular/angular.js/commit/b5af198f0d5b0f2b3ddb31ea12f700f3e0616271)) - correct isolate scope distribution to controllers ([3fe4491a](https://github.com/angular/angular.js/commit/3fe4491a6bf57ddeb312b8a30cf1706f6f1d2355)) - replaced element has isolate scope ([97c7a4e3](https://github.com/angular/angular.js/commit/97c7a4e3791d7cb05c3317cc5f0c49ab93810bf6)) - only pass isolate scope to children that belong to the isolate directive ([d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe)) - make isolate scope truly isolate ([909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7), [#1924](https://github.com/angular/angular.js/issues/1924), [#2500](https://github.com/angular/angular.js/issues/2500)) - don't instantiate controllers twice for element transclude directives ([18ae985c](https://github.com/angular/angular.js/commit/18ae985c3a3147b589c22f6ec21bacad2f578e2b), [#4654](https://github.com/angular/angular.js/issues/4654)) - attribute bindings should not break due to terminal directives ([79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8), [#4525](https://github.com/angular/angular.js/issues/4525), [#4528](https://github.com/angular/angular.js/issues/4528), [#4649](https://github.com/angular/angular.js/issues/4649)) - instantiate controlers when re-entering compilation ([faf5b980](https://github.com/angular/angular.js/commit/faf5b980da09da2b4c28f1feab33f87269f9f0ba), [#4434](https://github.com/angular/angular.js/issues/4434), [#4616](https://github.com/angular/angular.js/issues/4616)) - **$injector:** allow a constructor function to return a function ([c22adbf1](https://github.com/angular/angular.js/commit/c22adbf160f32c1839fbb35382b7a8c6bcec2927)) - **$parse:** check function call context to be safe ([6d324c76](https://github.com/angular/angular.js/commit/6d324c76f0d3ad7dae69ce01b14e0564938fb15e), [#4417](https://github.com/angular/angular.js/issues/4417)) - **angular-mocks:** add inline dependency annotation ([6d23591c](https://github.com/angular/angular.js/commit/6d23591c31f2b41097ceaa380af09998e4a62f09), [#4448](https://github.com/angular/angular.js/issues/4448)) - **animateSpec:** run digest to enable animations before tests ([aea76f0d](https://github.com/angular/angular.js/commit/aea76f0d5c43dc17f1319d0a45d2ce50fddf72e4)) - **bootstrap-prettify:** share $animate and $$postDigestQueue with demo apps ([1df3da36](https://github.com/angular/angular.js/commit/1df3da361d62726bf1dafe629a7fca845b6a8733)) - **csp:** - fix csp auto-detection and stylesheet injection ([08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351), [#917](https://github.com/angular/angular.js/issues/917), [#2963](https://github.com/angular/angular.js/issues/2963), [#4394](https://github.com/angular/angular.js/issues/4394), [#4444](https://github.com/angular/angular.js/issues/4444)) - don't inline css in csp mode ([a86cf20e](https://github.com/angular/angular.js/commit/a86cf20e67202d614bbcaf038c5e04db94483256) - **docModuleComponents:** implement anchor scroll when content added ([eb51b024](https://github.com/angular/angular.js/commit/eb51b024c9b77527420014cdf7dbb292b5b9dd6b), [#4703](https://github.com/angular/angular.js/issues/4703)) - **input:** keep track of min/max attars on-the-fly ([4b653aea](https://github.com/angular/angular.js/commit/4b653aeac1aca7ac551738870a2446b6810ca0df)) - **ngAnimate:** fix cancelChildAnimations throwing exception ([b9557b0a](https://github.com/angular/angular.js/commit/b9557b0a86206d938a738ea470736d011dff7e1a), [#4548](https://github.com/angular/angular.js/issues/4548)) - **ngClassSpec:** clear animation enable fn from postDigestQueue ([ffa9d0a6](https://github.com/angular/angular.js/commit/ffa9d0a6db137cba4090e569b8ed4e25a711314e)) - **ngEventDirectives:** parse expression only once during compile phase. ([9a828738](https://github.com/angular/angular.js/commit/9a828738cd2e959bc2a198989e96c8e416d28b71)) - **ngIf:** - destroy child scope when destroying DOM ([9483373c](https://github.com/angular/angular.js/commit/9483373c331343648e079420b3eb1f564d410ff2)) - ngIf removes elements dynamically added to it ([e19067c9](https://github.com/angular/angular.js/commit/e19067c9bbac3c3bb450c80f73eb5518bd0db1a1)) - **ngInclude:** only run anchorScroll after animation is done ([d378f550](https://github.com/angular/angular.js/commit/d378f5500ab2eef0779338336c6a95656505ebb8), [#4723](https://github.com/angular/angular.js/issues/4723)) - **ngMock:** throw more descriptive errors for $animate.flushNext() ([6fb19157](https://github.com/angular/angular.js/commit/6fb191570ee72f087e8bb6b1d8f5eea0f585886c)) - **ngModel:** deregister from the form on scope not DOM destruction ([8f989d65](https://github.com/angular/angular.js/commit/8f989d652f70fd147f66a18411070c7b939e242e), [#4226](https://github.com/angular/angular.js/issues/4226), [#4779](https://github.com/angular/angular.js/issues/4779)) - **ngScenario:** correctly disable animations for end 2 end tests ([9d004585](https://github.com/angular/angular.js/commit/9d0045856351e9db48ddf66f66e210d9cc53d24a)) - **ngView:** - only run anchorScroll after animation is done ([da344daa](https://github.com/angular/angular.js/commit/da344daa4023556f8abbef6d8ad87a16362b5861)) - ensure the new view element is placed after the old view element ([3f568b22](https://github.com/angular/angular.js/commit/3f568b22f9bec09192588e3cae937db5c2e757f9), [#4362](https://github.com/angular/angular.js/issues/4362)) - **ngdocs:** - create mock Doc objects correctly ([d4493fda](https://github.com/angular/angular.js/commit/d4493fda2c4c2ff1fdfc264bfb479741abc781c7)) - `shortDescription()` should not error if no `description` ([4c8fa353](https://github.com/angular/angular.js/commit/4c8fa353245b9c32261860caff18f002d294e19f)) - remove the side search bar ([6c20ec19](https://github.com/angular/angular.js/commit/6c20ec193f11aa647be1b2ad2ac5b3e7c2894bd7)) ## Breaking Changes - **$compile:** - due to [d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe), Child elements that are defined either in the application template or in some other directives template do not get the isolate scope. In theory, nobody should rely on this behavior, as it is very rare - in most cases the isolate directive has a template. - due to [909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7), Directives without isolate scope do not get the isolate scope from an isolate directive on the same element. If your code depends on this behavior (non-isolate directive needs to access state from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly. **Before** ``` .directive('ngIsolate', function() { return { scope: {}, template: '{{value}}' }; }); ``` **After** ``` .directive('ngIsolate', function() { return { scope: {value: '=ngModel'}, template: '{{value}} }; }); ``` Closes [#1924](https://github.com/angular/angular.js/issues/1924) and [#2500](https://github.com/angular/angular.js/issues/2500) - due to [79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8), Previously, the interpolation priority was `-100` in 1.2.0-rc.2, and `100` before 1.2.0-rc.2. Before this change the binding was setup in the post-linking phase. Now the attribute interpolation (binding) executes as a directive with priority 100 and the binding is set up in the pre-linking phase. Closes [#4525](https://github.com/angular/angular.js/issues/4525), [#4528](https://github.com/angular/angular.js/issues/4528), and [#4649](https://github.com/angular/angular.js/issues/4649) - **$parse:** due to [3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e), This commit introduces the notion of "private" properties (properties whose names begin and/or end with an underscore) on the scope chain. These properties will not be available to Angular expressions (i.e. {{ }} interpolation in templates and strings passed to `$parse`) They are freely available to JavaScript code (as before). **Motivation** Angular expressions execute in a limited context. They do not have direct access to the global scope, `window`, `document` or the Function constructor. However, they have direct access to names/properties on the scope chain. It has been a long standing best practice to keep sensitive APIs outside of the scope chain (in a closure or your controller.) That's easier said that done for two reasons: 1. JavaScript does not have a notion of private properties so if you need someone on the scope chain for JavaScript use, you also expose it to Angular expressions 2. the new "controller as" syntax that's now in increased usage exposes the entire controller on the scope chain greatly increaing the exposed surface. Though Angular expressions are written and controlled by the developer, they: 1. Typically deal with user input 2. Don't get the kind of test coverage that JavaScript code would This commit provides a way, via a naming convention, to allow publishing/restricting properties from controllers/scopes to Angular expressions enabling one to only expose those properties that are actually needed by the expressions. - **csp:** due to [08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351), triggering ngCsp directive via `ng:csp` attribute is not supported any more. Please use `data-ng-csp` instead. - **jqLite:** due to [27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b), `jqLite.scope()` (connonly used through `angular.element(node).scope()`) does not return the isolate scope on the element that triggered directive with isolate scope. Use `jqLite.isolateScope()` instead. # 1.2.0-rc.3 ferocious-twitch (2013-10-14) ## Features - **$interval:** add a service wrapping setInterval ([2b5ce84f](https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83)) - **$sce:** simpler patterns for `$sceDelegateProviders` white/blacklists ([93ce5923](https://github.com/angular/angular.js/commit/93ce5923e92f6d2db831d8715ec62734821c70ce), [#4006](https://github.com/angular/angular.js/issues/4006)) - **$filter:** allow map of filters to be registered ([4033cf28](https://github.com/angular/angular.js/commit/4033cf28142664c52aa7b4bc95340ac913397ac8), [#4036](https://github.com/angular/angular.js/issues/4036), [#4091](https://github.com/angular/angular.js/issues/4091)) - **$compile:** support `tel:` links in `a[href]` ([e7730297](https://github.com/angular/angular.js/commit/e773029717f11d727af609a139b173a135c79eab)) - **Directives:** - **ngRepeat:** support repeating over `ngInclude` and other directives that replace repeated nodes ([9efa46ae](https://github.com/angular/angular.js/commit/9efa46ae640cde17487c341daa9a75c0bd79da02), [#3104](https://github.com/angular/angular.js/issues/3104)) - **event directives:** add `ngCopy`, `ngCut`, and `ngPaste` ([147c6929](https://github.com/angular/angular.js/commit/147c6929a264a7b077a5f2cfc5aa9a0b9513acd7), [#4172](https://github.com/angular/angular.js/issues/4172)) - **Misc:** - jQuery 1.10.x support ([e0c134b8](https://github.com/angular/angular.js/commit/e0c134b8bfa282379daec6a7137512d58f956443), [#3764](https://github.com/angular/angular.js/issues/3764)) - **minErr:** linkify error messages on minErr docs pages ([6aaae062](https://github.com/angular/angular.js/commit/6aaae062171bfc8e5046c3eae99bc9d63037120a)) - **tutorial:** add step 12 on animations to the phonecat tutorial ([ad525645](https://github.com/angular/angular.js/commit/ad5256452bb8f1d481d78e7ae15a59d288f0d8e9)) ## Bug Fixes - **$compile:** - abort compilation when duplicate element transclusion ([63c5334c](https://github.com/angular/angular.js/commit/63c5334c84b7269428c710226764d1f08a36e0d4), [#3893](https://github.com/angular/angular.js/issues/3893), [#4217](https://github.com/angular/angular.js/issues/4217), [#3307](https://github.com/angular/angular.js/issues/3307)) - make order directives w/ same priority deterministic ([4357da85](https://github.com/angular/angular.js/commit/4357da857587d3c28790e7dc654664bec5808768)) - fix (reverse) directive postLink fn execution order ([31f190d4](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b), [#3558](https://github.com/angular/angular.js/issues/3558)) - don't terminate compilation for regular transclusion directives ([fe214501](https://github.com/angular/angular.js/commit/fe2145016cb057c92f9f01b32c58b4d7259eb6ee)) - ng-attr to support dash separated attribute names ([8e6e3eba](https://github.com/angular/angular.js/commit/8e6e3ebad991eaf57a7885549ea3b91932d495c9)) - allow interpolations for non-event handlers attrs ([8e1276c0](https://github.com/angular/angular.js/commit/8e1276c011b33b90af47494dc5e76baf86468a5a)) - link parents before traversing ([742271ff](https://github.com/angular/angular.js/commit/742271ffa3a518d9e8ef2cb97c24b45b44e3378d), [#3792](https://github.com/angular/angular.js/issues/3792), [#3923](https://github.com/angular/angular.js/issues/3923), [#3935](https://github.com/angular/angular.js/issues/3935), [#3927](https://github.com/angular/angular.js/issues/3927)) - collect ranges on multiple directives on one element ([6a8edc1d](https://github.com/angular/angular.js/commit/6a8edc1d43aca7c5a92f86309b1bb1d5f9968442), [#4002](https://github.com/angular/angular.js/issues/4002)) - **$parse:** - deprecate promise unwrapping and make it an opt-in ([5dc35b52](https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577), [#4158](https://github.com/angular/angular.js/issues/4158), [#4270](https://github.com/angular/angular.js/issues/4270)) - disallow access to window and dom in expressions ([be0b4856](https://github.com/angular/angular.js/commit/be0b4856699334ff51bacf2d1fd3394663d6bd28)) - **$httpBackend:** - set headers with falsy values ([e9a22241](https://github.com/angular/angular.js/commit/e9a222418a029d830698444cf95bf13f8ad75805), [#2984](https://github.com/angular/angular.js/issues/2984)) - don't send empty string bodies ([0d0330ad](https://github.com/angular/angular.js/commit/0d0330adc24a68cd6891a030a56d3ce3bbced03c), [#2149](https://github.com/angular/angular.js/issues/2149)) - **$location:** - prevent infinite digest error in IE7 ([d7071148](https://github.com/angular/angular.js/commit/d70711481e6311c9cd283d650f07ca0cca72ecc2), [#2802](https://github.com/angular/angular.js/issues/2802)) - re-assign location after BFCache back ([2ebf9316](https://github.com/angular/angular.js/commit/2ebf93163027abc55ba27f673be3b8dc1281c068), [#4044](https://github.com/angular/angular.js/issues/4044)) - **$log:** prevent logging `undefined` for $log in IE ([4ff1a650](https://github.com/angular/angular.js/commit/4ff1a65031e985bf930f6761c1ecf46e4db98d6e), [#1705](https://github.com/angular/angular.js/issues/1705)) - **Scope:** - `$evalAsync` executes on the right scope ([10cc1a42](https://github.com/angular/angular.js/commit/10cc1a42c925749f88433546d41d35ba07a88e6f)) - make `stopPropagation` only stop its own event ([47f7bd70](https://github.com/angular/angular.js/commit/47f7bd706efc5f2944d182e46c1b1d324298ff36), [#4204](https://github.com/angular/angular.js/issues/4204)) - **Filters:** - **date:** allow negative millisecond value strings ([025c9219](https://github.com/angular/angular.js/commit/025c92190376414c15f15fd20a75b41489a4e70a)) - **Directives:** - correct priority of structural directives (ngRepeat, ngSwitchWhen, ngIf, ngInclude, ngView) ([b7af76b4](https://github.com/angular/angular.js/commit/b7af76b4c5aa77648cc1bfd49935b48583419023)) - **input:** `false` is no longer an empty value by default ([b56b21a8](https://github.com/angular/angular.js/commit/b56b21a898b3c77589a48a290271f9dc181dafe8), [#3490](https://github.com/angular/angular.js/issues/3490)) - **ngBindHtml:** watch string value instead of wrapper ([e2068ad4](https://github.com/angular/angular.js/commit/e2068ad426075ac34c06c12e2fac5f594cc81969), [#3932](https://github.com/angular/angular.js/issues/3932)) - **ngOptions:** ignore object properties which start with $ ([aa3c54c7](https://github.com/angular/angular.js/commit/aa3c54c73f7470999535294899a1c33cd193f455)) - **ngRepeat:** correctly track elements even when the collection is initially undefined ([31c56f54](https://github.com/angular/angular.js/commit/31c56f540045b5270f5b8e235873da855caf3486), [#4145](https://github.com/angular/angular.js/issues/4145), [#3964](https://github.com/angular/angular.js/issues/3964)) - **ngTransclude:** detect ngTranslude usage without a transclusion directive ([5a1a6b86](https://github.com/angular/angular.js/commit/5a1a6b86a8dbcd8aa4fe9c59fad8d005eead686c), [#3759](https://github.com/angular/angular.js/issues/3759)) - **jqLite:** - ignore class methods on comment elements ([64fd2c42](https://github.com/angular/angular.js/commit/64fd2c421ed582c16812d164a8a6f031b8e66287)) - use get/setAttribute so that jqLite works on SVG nodes ([c785267e](https://github.com/angular/angular.js/commit/c785267eb8780d8b7658ef93ebb5ebddd566294d), [#3858](https://github.com/angular/angular.js/issues/3858)) - **Misc:** - **isArrayLike:** correctly handle string primitives ([5b8c7884](https://github.com/angular/angular.js/commit/5b8c78843e8d62a7a67cead8bf04c76aa8ee411d), [#3356](https://github.com/angular/angular.js/issues/3356)) - protect calls to hasOwnProperty in public API ([7a586e5c](https://github.com/angular/angular.js/commit/7a586e5c19f3d1ecc3fefef084ce992072ee7f60), [#3331](https://github.com/angular/angular.js/issues/3331)) - **ngRoute:** - **ngView:** IE8 regression due to expando on non-element nodes ([255e8c13](https://github.com/angular/angular.js/commit/255e8c13cf0fd78f1c4d7c279be7bf47c2402956), [#3971](https://github.com/angular/angular.js/issues/3971)) - **$route:** parametrized routes do not match against locations that would not valorize each parameters. ([0ff86c32](https://github.com/angular/angular.js/commit/0ff86c323359fba1a60bacab178e3c68528f8e1f)) - **ngResource:** - pass transformed value to both callbacks and promises ([e36e28eb](https://github.com/angular/angular.js/commit/e36e28ebd4a6c144e47d11fba8e211d8d5a9d03e), [#3817](https://github.com/angular/angular.js/issues/3817)) - remove request body from $delete ([8336b3a2](https://github.com/angular/angular.js/commit/8336b3a286f8469d4cd7c412c41ca8c1a31fecf0), [#4280](https://github.com/angular/angular.js/issues/4280)) - **ngSanitize:** - sanitize DOCTYPE declarations correctly ([e66c23fe](https://github.com/angular/angular.js/commit/e66c23fe55f8571a014b0686c8dbca128e7a8240), [#3931](https://github.com/angular/angular.js/issues/3931)) - sanitizer should not accept as a valid comment ([21e9e8cf](https://github.com/angular/angular.js/commit/21e9e8cf68ef007136da6cc212d2f1f252fb668a)) - **ngTouch:** - ngClick does not pass touchend event when jQuery is loaded ([9fd92cc3](https://github.com/angular/angular.js/commit/9fd92cc3c93a6378e8887fd46fd4ad182a375544)) - add $event to ng-swipe ([507d8021](https://github.com/angular/angular.js/commit/507d8021b1c91cc0cefc0418e61b04597ad1030b), [#4071](https://github.com/angular/angular.js/issues/4071), [#4321](https://github.com/angular/angular.js/issues/4321)) - **ngAnimate:** - ensure that a timeStamp is created if not provided by the browser event ([cd216c4c](https://github.com/angular/angular.js/commit/cd216c4c30adfebb3ef633f18fab2d98e8c52ebc), [#3053](https://github.com/angular/angular.js/issues/3053)) - perform internal caching on getComputedStyle to boost the performance of CSS3 transitions/animations ([b1e604e3](https://github.com/angular/angular.js/commit/b1e604e38ceec1714174fb54cc91590a7fe99a92), [#4011](https://github.com/angular/angular.js/issues/4011), [#4124](https://github.com/angular/angular.js/issues/4124)) - ensure structural animations skip all child animations even if no animation is present during compile ([cc584607](https://github.com/angular/angular.js/commit/cc5846073e57ef190182026d7e5a8e2770d9b770), [#3215](https://github.com/angular/angular.js/issues/3215)) - cancel any ongoing child animations during move and leave animations ([3f31a7c7](https://github.com/angular/angular.js/commit/3f31a7c7691993893f0724076816f6558643bd91)) - ensure elapsedTime always considers delay values ([079dd939](https://github.com/angular/angular.js/commit/079dd93991ac79b5f9af6efb7fe2b3600195f10c)) - ensure transition-property is not changed when only keyframe animations are in use ([2df3c9f5](https://github.com/angular/angular.js/commit/2df3c9f58def9584455f7c4bfdabbd12aab58bf9), [#3933](https://github.com/angular/angular.js/issues/3933)) - avoid completing the animation asynchronously unless CSS transtiions/animations are present ([2a63dfa6](https://github.com/angular/angular.js/commit/2a63dfa6cc7889888f4296fff2944e74ff30b3af), [#4023](https://github.com/angular/angular.js/issues/4023), [#3940](https://github.com/angular/angular.js/issues/3940)) - ensure that delays are always considered before an animation closes ([0a63adce](https://github.com/angular/angular.js/commit/0a63adce687d28ada90ea930d5e69883cc11cba5), [#4028](https://github.com/angular/angular.js/issues/4028)) - check elapsedTime on current event ([d50ed6bf](https://github.com/angular/angular.js/commit/d50ed6bfb8c4982401923ff535fe932ef4f387a2)) - support addClass/removeClass animations on SVG nodes ([c785267e](https://github.com/angular/angular.js/commit/c785267eb8780d8b7658ef93ebb5ebddd566294d), [#3858](https://github.com/angular/angular.js/issues/3858)) - **ngScenario:** - remove redundant assignment ([a80e96ce](https://github.com/angular/angular.js/commit/a80e96cea184b392505f0a292785a5c66d45e165), [#4315](https://github.com/angular/angular.js/issues/4315)) - fix error message description ([f8f8f754](https://github.com/angular/angular.js/commit/f8f8f754b02459bb789247476cc0da63d2d7370f)) - provide event parameters as object ([28f56a38](https://github.com/angular/angular.js/commit/28f56a383e9d1ff378e3568a3039e941c7ffb1d8)) - include "not " in error messages if test is inverted ([3589f178](https://github.com/angular/angular.js/commit/3589f17824376e9db4e8d002caeb4483943eeb18), [#3840](https://github.com/angular/angular.js/issues/3840)) ## Breaking Changes - **$compile:** due to [31f190d4](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b), the order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute. Previously the compile/link fns executed in this order controlled via priority: - CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow - compile child nodes - PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow - link child nodes - PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow This was changed to: - CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow - compile child nodes - PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow - link child nodes - PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh Very few directives in practice rely on order of postLinking function (unlike on the order of compile functions), so in the rare case of this change affecting an existing directive, it might be necessary to convert it to a preLinking function or give it negative priority (look at the diff of this commit to see how an internal attribute interpolation directive was adjusted). - **$parse:** - due to [5dc35b52](https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577), $parse and templates in general will no longer automatically unwrap promises. This feature has been deprecated and if absolutely needed, it can be reenabled during transitional period via `$parseProvider.unwrapPromises(true)` api. - due to [b6a37d11](https://github.com/angular/angular.js/commit/b6a37d112b3e1478f4d14a5f82faabf700443748), feature added in rc.2 that unwraps return values from functions if the values are promises (if promise unwrapping is enabled - see previous point), was reverted due to breaking a popular usage pattern. - **directives:** due to [b7af76b4](https://github.com/angular/angular.js/commit/b7af76b4c5aa77648cc1bfd49935b48583419023), the priority of ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView has changed. This could affect directives that explicitly specify their priority. In order to make ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView work together in all common scenarios their directives are being adjusted to achieve the following precendence: ``` Directive | Old Priority | New Priority ============================================= ngRepeat | 1000 | 1000 --------------------------------------------- ngSwitchWhen | 500 | 800 --------------------------------------------- ngIf | 1000 | 600 --------------------------------------------- ngInclude/ngView | 1000 | 400 ``` - **form/ngForm** due to [7a586e5c](https://github.com/angular/angular.js/commit/7a586e5c19f3d1ecc3fefef084ce992072ee7f60), Inputs with name equal to "hasOwnProperty" are not allowed inside form or ngForm directives. Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added to the scope. Now a badname exception is thrown. Using "hasOwnProperty" for an input name would be very unusual and bad practice. Either do not include such an input in a `form` or `ngForm` directive or change the name of the input. - **ngScenario:** due to [28f56a38](https://github.com/angular/angular.js/commit/28f56a383e9d1ff378e3568a3039e941c7ffb1d8), browserTrigger now uses an eventData object instead of direct parameters for mouse events. To migrate, place the `keys`,`x` and `y` parameters inside of an object and place that as the third parameter for the browserTrigger function. # 1.2.0-rc.2 barehand-atomsplitting (2013-09-04) ## Features - **Scope:** asynchronously auto-flush `$evalAsync` queue when outside of `$digest` cycle ([6b91aa0a](https://github.com/angular/angular.js/commit/6b91aa0a18098100e5f50ea911ee135b50680d67), [#3539](https://github.com/angular/angular.js/issues/3539), [#2438](https://github.com/angular/angular.js/issues/2438)) - **minErr:** log minerr doc url in development builds ([37123cd2](https://github.com/angular/angular.js/commit/37123cd2858b4e318ed8109af745312df4848577), [#3566](https://github.com/angular/angular.js/issues/3566)) - **ngMock:** - allow passing an object literal as shorthand to module ([f737c97d](https://github.com/angular/angular.js/commit/f737c97df02918eb5b19bf5c8248fa3e20f9b361)) - add support for creating dynamic style sheets within test code ([fb3a7db0](https://github.com/angular/angular.js/commit/fb3a7db0809b959d50be4cb93a65a91200071dd5)) ## Bug Fixes - **$http:** allow empty responses to be cached ([8e48c4ff](https://github.com/angular/angular.js/commit/8e48c4ff6abf7083a04cf20312d2b106f4ba5b2c), [#3809](https://github.com/angular/angular.js/issues/3809)) - **$injector:** don't parse fns with no args ([44b6b72e](https://github.com/angular/angular.js/commit/44b6b72e5e9d193ec878ac7a4f25a00815f68cca)) - **$parse:** handle promises returned from parsed function calls ([3a658220](https://github.com/angular/angular.js/commit/3a65822023119b71deab5e298c7ef2de204caa13), [#3503](https://github.com/angular/angular.js/issues/3503)) - **$q:** - reject should catch & forward exceptions thrown in error callbacks ([5d9f4205](https://github.com/angular/angular.js/commit/5d9f42050a11015adbd5dc4dde73818919e93a99)) - fix forwarding resolution when callbacks aren't functions ([7d188d63](https://github.com/angular/angular.js/commit/7d188d630c63fde05d8765d0ad2d75a5baa8e5d3), [#3535](https://github.com/angular/angular.js/issues/3535)) - **$location:** fix history problems on Boxee box ([eefcdad0](https://github.com/angular/angular.js/commit/eefcdad013b56d5d3a05c0b2137a5860091b2575)) - **$timeout:** clean deferreds immediately after callback exec/cancel ([920a3804](https://github.com/angular/angular.js/commit/920a3804136d49cdaf7bc2712f5832bc50409dc9)) - **Directives:** - **ngTransclude:** - clear the translusion point before transcluding ([eed299a3](https://github.com/angular/angular.js/commit/eed299a31b5a6dd0363133c5f9271bf33d090c94)) - make the transclusion available to parent post-link function ([bf79bd41](https://github.com/angular/angular.js/commit/bf79bd4194eca2118ae1c492c08dbd217f5ae810)) - **ngView:** ensure `ngClass` works with together with `ngView`'s transclusion behavior ([40c0220c](https://github.com/angular/angular.js/commit/40c0220c47c620070b30aec6ec4552c68a8689eb)) - **Filters:** - **filter:** filter on false properties ([3bc4e7fd](https://github.com/angular/angular.js/commit/3bc4e7fd20372c0cad8298bff019b32681b16026), [#2797](https://github.com/angular/angular.js/issues/2797)) - **orderBy:** remove redundant if statement ([5e45fd4a](https://github.com/angular/angular.js/commit/5e45fd4ac6ff7c00d34deb099fca12301cafd7b0)) - **Misc:** - parse IE11 UA string correctly ([427ee93f](https://github.com/angular/angular.js/commit/427ee93f11d0ef64b8844f9b43b2a0f21f2be2cb), [#3682](https://github.com/angular/angular.js/issues/3682)) - **i18n:** remove obsolete locale files ([6382e21f](https://github.com/angular/angular.js/commit/6382e21fb28541a2484ac1a241d41cf9fbbe9d2c)) - **ngAnimate:** - ensure that `ngClass` is always compiled before enter, leave and move animations are applied ([36ad40b1](https://github.com/angular/angular.js/commit/36ad40b18cfdd0690411a5169aa94e222946b5cf), [#3727](https://github.com/angular/angular.js/issues/3727), [#3603](https://github.com/angular/angular.js/issues/3603)) - cut down on extra `$timeout` calls ([4382df03](https://github.com/angular/angular.js/commit/4382df03fa1962aed027742c1b463406c40653c9)) - skip `ngAnimate` animations if the provided element already has transitions applied to it ([7c605ddf](https://github.com/angular/angular.js/commit/7c605ddf1c57c9f162827713ca5b0fbb12de5fa5), [#3587](https://github.com/angular/angular.js/issues/3587)) - only apply a timeout when transitions or keyframe animations are used ([ee2f3d21](https://github.com/angular/angular.js/commit/ee2f3d21da6c9fccfe1e6a4ea8a65627519c8bf2), [#3613](https://github.com/angular/angular.js/issues/3613)) - ensure older versions of webkit work for animations ([b1a43cd0](https://github.com/angular/angular.js/commit/b1a43cd04e8727df5bef3197f5fda3b98ecab740)) - **ngMocks:** `$logProvider` should not use internal APIs ([baaa73ee](https://github.com/angular/angular.js/commit/baaa73ee1ef25fa506ff7aaab3159d710acdafdb), [#3612](https://github.com/angular/angular.js/issues/3612)) ## Breaking Changes - **i18n:** due to [6382e21f](https://github.com/angular/angular.js/commit/6382e21fb28541a2484ac1a241d41cf9fbbe9d2c), some uncommon region-specific local files were removed. # 1.0.8 bubble-burst (2013-08-22) Contains only these fixes cherry-picked from [v1.2.0rc1](#1.2.0rc1). ## Bug Fixes - **$compile:** - don't check attr.specified on non-ie7 ([78efa0e3](https://github.com/angular/angular.js/commit/78efa0e36c1cb9fe293190381baa5a3fe5b3d1cb), [#3231](https://github.com/angular/angular.js/issues/3231), [#2160](https://github.com/angular/angular.js/issues/2160)) - empty normalized href should pass sanitation check ([3b2c6f09](https://github.com/angular/angular.js/commit/3b2c6f09cb857b86641cefde5b92d84d58c1118d), [#2219](https://github.com/angular/angular.js/issues/2219)) - **$http:** ensure case-insensitive header overriding ([25d9f5a8](https://github.com/angular/angular.js/commit/25d9f5a804b7a6a61db6e84e594b1b5fe7ea14bf)) - **$location:** - default to / for the url base if no `base[href]` ([cbe31d8d](https://github.com/angular/angular.js/commit/cbe31d8dfd12ce973c574bfc825ffc0ffb8eb7c4), [#2762](https://github.com/angular/angular.js/issues/2762)) - prevent infinite digest error due to IE bug ([97abb124](https://github.com/angular/angular.js/commit/97abb124738e0ca5d00d807d65c482f7890feadd), [#2802](https://github.com/angular/angular.js/issues/2802)) - don't crash on invalid query parameters ([b9dcb35e](https://github.com/angular/angular.js/commit/b9dcb35e9bc64cb2f48f3a349ead66c501cbdc48)) - **$parse:** move global getter out of parse.js ([099138fb](https://github.com/angular/angular.js/commit/099138fb9a94178d3d82568fbda28d0c87443de9)) - **$q:** call `reject()` even if `$exceptionHandler` rethrows ([d59027c4](https://github.com/angular/angular.js/commit/d59027c40ed73fa9e114706d0c5a885785311dec)) - **$timeout:** clean deferreds immediately after callback exec/cancel ([ac69392c](https://github.com/angular/angular.js/commit/ac69392cd7f939ebbd37765e377051d4c05df4a5)) - **$sanitize:** match URI schemes case-insensitively ([fcd761b9](https://github.com/angular/angular.js/commit/fcd761b9d7c3c91673efce9b980ac5e7973adf3d), [#3210](https://github.com/angular/angular.js/issues/3210)) - **Scope:** watches can be safely unregistered inside watch handlers ([a4ec2979](https://github.com/angular/angular.js/commit/a4ec297925f052bf9ea1aba9f584eaaf7472fb93), [#2915](https://github.com/angular/angular.js/issues/2915)) - **ngMock** - $timeout should forward delay argument ([a5fb372e](https://github.com/angular/angular.js/commit/a5fb372e1e6aed8cdb1f572f1df3d6fe89388f3e)) - **jqLite:** - return array from multi select in val() ([01cd3495](https://github.com/angular/angular.js/commit/01cd34957e778a2fa8d26e2805c2dd5a7f986465)) - forgive unregistration of a non-registered handler ([ac5b9055](https://github.com/angular/angular.js/commit/ac5b9055f6d7224e5e8e49941c0fc9cb16c64a7e)) - prepend array in correct order ([63414b96](https://github.com/angular/angular.js/commit/63414b965397a9fd7d2f49e8dea4b848e0d6707e)) - correctly monkey-patch core jQuery methods ([815053e4](https://github.com/angular/angular.js/commit/815053e403ace666b2383643227ecde5f36742c5)) - **Directives:** - **form:** pick the right attribute name for ngForm ([dc1e55ce](https://github.com/angular/angular.js/commit/dc1e55ce1a314b6c1ad4b9d5b4a31226e1fa1e18), [#2997](https://github.com/angular/angular.js/issues/2997)) - **input:** fix the email regex to accept TLDs up to 6 characters long ([ad76e77f](https://github.com/angular/angular.js/commit/ad76e77fce09d0aee28b5ca1a328d5df8596b935)) - **ngCloak:** hide element even when CSS 'display' is set ([06b0930b](https://github.com/angular/angular.js/commit/06b0930b6a821bdfed78875f821baf1b8ede2442)) - **ngSubmit:** expose $event to ngSubmit callback ([b0d5f062](https://github.com/angular/angular.js/commit/b0d5f062e316370c7ac57cfd628d085015a8187d)) - **ngValue:** made ngValue to write value attribute to element ([3b898664](https://github.com/angular/angular.js/commit/3b898664eea9913b6b25261d7310a61de476d173)) - **Filters:** - **number:** always convert scientific notation to decimal ([408e8682](https://github.com/angular/angular.js/commit/408e868237d80f9332f2c540f91b2809d9938fbc)) - **orderBy:** remove redundant if statement ([ec1cece2](https://github.com/angular/angular.js/commit/ec1cece270e293e7c55556fc68afee9a2ad40641)) - **i18n:** Do not transform arrays into objects ([751c77f8](https://github.com/angular/angular.js/commit/751c77f87b34389c5b85a23c71080d367c42d31b)) - **jqLite:** - return array from multi select in val() ([01cd3495](https://github.com/angular/angular.js/commit/01cd34957e778a2fa8d26e2805c2dd5a7f986465)) - forgive unregistration of a non-registered handler ([ac5b9055](https://github.com/angular/angular.js/commit/ac5b9055f6d7224e5e8e49941c0fc9cb16c64a7e)) - prepend array in correct order ([63414b96](https://github.com/angular/angular.js/commit/63414b965397a9fd7d2f49e8dea4b848e0d6707e)) - correctly monkey-patch core jQuery methods ([815053e4](https://github.com/angular/angular.js/commit/815053e403ace666b2383643227ecde5f36742c5)) - **Misc:** - **angular.copy:** change angular.copy to correctly clone RegExp ([5cca077e](https://github.com/angular/angular.js/commit/5cca077e4a40a26cc2deee2a86a215f575f25b22), [#3473](https://github.com/angular/angular.js/issues/3473), [#3474](https://github.com/angular/angular.js/issues/3474)) - **angular.equals:** - add support for regular expressions ([a357649d](https://github.com/angular/angular.js/commit/a357649da5d9f0633fa8e8a249f58dfc1105698e), [#2685](https://github.com/angular/angular.js/issues/2685)) - {} and [] should not be considered equivalent ([da1f7c76](https://github.com/angular/angular.js/commit/da1f7c762d36b646c107260f74daf3a0ab5f91f5)) - **angular.toJson:** skip JSON.stringify for undefined ([332a3c79](https://github.com/angular/angular.js/commit/332a3c7984229a7e3a9a8a277f92942299616fdb)) # 1.2.0rc1 spooky-giraffe (2013-08-13) [Full Commit Log](https://github.com/angular/angular.js/compare/v1.1.5...master) ## Features - **ngAnimate:** complete rewrite of animations ([81923f1e](https://github.com/angular/angular.js/commit/81923f1e41560327f7de6e8fddfda0d2612658f3)) - **$sce:** new $sce service for Strict Contextual Escaping and lots of other security enhancements ([bea9422e](https://github.com/angular/angular.js/commit/bea9422ebfc8e80ee28ad81afc62d2e432c85cbb)) - **minErr:** add error message minification and better error messages ([c8fcf3b3](https://github.com/angular/angular.js/commit/c8fcf3b369dbe866815e18e0fa4d71f3e679bc5f), [09fa0656](https://github.com/angular/angular.js/commit/09fa0656b49321681f28453abef566d0cbe0eb22), [b8ea7f6a](https://github.com/angular/angular.js/commit/b8ea7f6aba2e675b85826b0bee1f21ddd7b866a5)) - **$compile:** - support animation hooks bindings to class attributes ([f2dfa891](https://github.com/angular/angular.js/commit/f2dfa8916f8ed855d55187f5400c4c2566ce9a1b)) - support multi-element directive ([e46100f7](https://github.com/angular/angular.js/commit/e46100f7097d9a8f174bdb9e15d4c6098395c3f2)) - support "Controller as" instance syntax for directives ([b3777f27](https://github.com/angular/angular.js/commit/b3777f275c6bd2bd4a88963fd03828eb7cf3aca8)) - **$http:** accept function as headers value ([a7150f12](https://github.com/angular/angular.js/commit/a7150f1256f2a97a931b3c0d16eab70f45e81cae)) - **$q:** - add `.catch()` as shorthand for defining promise error handlers ([a207665d](https://github.com/angular/angular.js/commit/a207665dad69248139b150cd3fe8ba13059bffb4), [#2048](https://github.com/angular/angular.js/issues/2048), [#3476](https://github.com/angular/angular.js/issues/3476)) - added support for promise notification ([2a5c3555](https://github.com/angular/angular.js/commit/2a5c3555829da51f55abd810a828c73b420316d3)) - **$resource:** - support an unescaped URL port in the url template ([b94ca12f](https://github.com/angular/angular.js/commit/b94ca12fa0b027d8592f5717e038b7b116c59384), [#2778](https://github.com/angular/angular.js/issues/2778)) - expose promise as `$promise` instead of only `$then` ([05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d)) - **$route:** express style route matching (support for optional params and new wildcard syntax) ([04cebcc1](https://github.com/angular/angular.js/commit/04cebcc133c8b433a3ac5f72ed19f3631778142b)) - **jqLite:** switch bind/unbind to more recent jQuery on/off ([f1b94b4b](https://github.com/angular/angular.js/commit/f1b94b4b599ab701bc75b55bbbbb73c5ef329a93)) - **Misc:** - add source maps to all min files ([908071af](https://github.com/angular/angular.js/commit/908071afbf32c46fe9110e4a67e104bbd4b3a56b), [#1714](https://github.com/angular/angular.js/issues/1714)) - **Directives:** - add `ngFocus` and `ngBlur` directives ([2bb27d49](https://github.com/angular/angular.js/commit/2bb27d4998805fd89db25192f53d26d259ae615f), [#1277](https://github.com/angular/angular.js/issues/1277)) - **ngRepeat:** add $even and $odd props to iterator ([52b8211f](https://github.com/angular/angular.js/commit/52b8211fd0154b9d6b771a83573a161f5580d92c)) - **ngForm:** supports namespaces in form names ([8ea802a1](https://github.com/angular/angular.js/commit/8ea802a1d23ad8ecacab892a3a451a308d9c39d7)) - **ngBindHtml:** combine ng-bind-html and ng-bind-html-unsafe ([dae69473](https://github.com/angular/angular.js/commit/dae694739b9581bea5dbc53522ec00d87b26ae55)) - **ngPluralize:** add alternative mapping using attributes ([a170fc1a](https://github.com/angular/angular.js/commit/a170fc1a749effa98bfd1c2e1b30297ed47b451b), [#2454](https://github.com/angular/angular.js/issues/2454)) - **ngMobile/ngTouch:** - emit `swipeleft` and `swiperight` events ([ab189142](https://github.com/angular/angular.js/commit/ab189142988043d0513bb796c3b54ca7d07f242d)) - refactor swipe logic from `ngSwipe` directive to `$swipe` service. ([f4c6b2c7](https://github.com/angular/angular.js/commit/f4c6b2c7894cb2d82ac69a1500a27785360b81c3)) - **ngMock:** - $timeout.flushNext can expect specific timeout delays ([462ed033](https://github.com/angular/angular.js/commit/462ed033d512ae94cb188efc9453de84ace4e17e)) - support delay limit for $timeout.flush ([b7fdabc4](https://github.com/angular/angular.js/commit/b7fdabc4bf2a9dd11a57f98c5229d834c4589bab)) - support a matching function for data param ([08daa779](https://github.com/angular/angular.js/commit/08daa7797bce5207916251d4a0ab3d5c93e5529a)) - **scenario:** expose jQuery for usage outside of angular scenario ([3fdbe81a](https://github.com/angular/angular.js/commit/3fdbe81a337c39027929c415e719493755cd8583)) - **ngDocs:** - provide support for user to jump between different versions of the angularjs doc ([46dfb92a](https://github.com/angular/angular.js/commit/46dfb92afd185c93f60ca90a72653f33d7cb18e8)) - add links to source for API ([52d6a599](https://github.com/angular/angular.js/commit/52d6a5990225439ac9141398d83e0d4e6134b576)) - support popover, foldouts and foldover annotations ([ef229688](https://github.com/angular/angular.js/commit/ef22968810d555f78d3bbf7b5428757690c8cc70)) - provide documentation for the new ngRepeat repeater syntax ([b3650457](https://github.com/angular/angular.js/commit/b36504577c538b745e6270e77d86af90285e2ae6)) - provide support for inline variable hinting ([21c70729](https://github.com/angular/angular.js/commit/21c70729d9269de85df3434c431c2f18995b0f7b)) ## Bug Fixes - **$compile:** - correct controller instantiation for async directives ([c173ca41](https://github.com/angular/angular.js/commit/c173ca412878d537b18df01f39e400ea48a4b398), [#3493](https://github.com/angular/angular.js/issues/3493), [#3482](https://github.com/angular/angular.js/issues/3482), [#3537](https://github.com/angular/angular.js/issues/3537), [#3540](https://github.com/angular/angular.js/issues/3540)) - always instantiate controllers before pre-link fns run ([5c560117](https://github.com/angular/angular.js/commit/5c560117425e7b3f7270389274476e843d6f69ec), [#3493](https://github.com/angular/angular.js/issues/3493), [#3482](https://github.com/angular/angular.js/issues/3482), [#3514](https://github.com/angular/angular.js/issues/3514)) - always instantiate controllers in parent->child order ([45f9f623](https://github.com/angular/angular.js/commit/45f9f62367221b2aa097ba1d87d744e50140ddc7), [#2738](https://github.com/angular/angular.js/issues/2738)) - don't check attr.specified on non-ie7 ([f9ea69f6](https://github.com/angular/angular.js/commit/f9ea69f6567c22ff328fd1f7b07847883757bfa6), [#3231](https://github.com/angular/angular.js/issues/3231), [#2160](https://github.com/angular/angular.js/issues/2160)) - allow `data:` image URIs in `img[src]` bindings ([3e39ac7e](https://github.com/angular/angular.js/commit/3e39ac7e1b10d4812a44dad2f959a93361cd823b)) - empty normalized href url should pass sanitation check ([fc8c9baa](https://github.com/angular/angular.js/commit/fc8c9baa399c33956133cdb6892fc7007430d299), [#2219](https://github.com/angular/angular.js/issues/2219)) - prevent infinite loop w/ replace+transclude directives ([69f42b76](https://github.com/angular/angular.js/commit/69f42b76548d00f52b231ec91150e4f0b008c730), [#2155](https://github.com/angular/angular.js/issues/2155)) - reject multi-expression interpolations for `src` attribute ([38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262bb43f26d47245f6c2739)) - disallow interpolations for DOM event handlers ([39841f2e](https://github.com/angular/angular.js/commit/39841f2ec9b17b3b2920fd1eb548d444251f4f56)) - sanitize values bound to `img[src]` ([1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d61286840177607edd552a9df97)) - support multi-element group over text nodes ([b28f9694](https://github.com/angular/angular.js/commit/b28f96949ac477b1fe43c81df7cedc21c7ab184c)) - correct component transclusion on compilation root. ([15e1a29c](https://github.com/angular/angular.js/commit/15e1a29cd08993b599f390e83a249ec17f753972)) - **$http:** - allow interceptors to completely override headers ([514dc0eb](https://github.com/angular/angular.js/commit/514dc0eb16a8fe3fa7c44094d743714f73754321), [#2770](https://github.com/angular/angular.js/issues/2770)) - treat headers as case-insensitive when overriding defaults ([53359d54](https://github.com/angular/angular.js/commit/53359d549e364759d5b382c229f7d326799bf418)) - **$location:** - don't initialize url hash in hashbang mode unnecessarily ([d4d34aba](https://github.com/angular/angular.js/commit/d4d34aba6efbd98050235f5b264899bb788117df)) - prevent infinite digest error due to IE bug ([dca23173](https://github.com/angular/angular.js/commit/dca23173e25a32cb740245ca7f7b01a84805f43f), [#2802](https://github.com/angular/angular.js/issues/2802)) - in html5 mode, default to / for the url base if no `base[href]` ([aef09800](https://github.com/angular/angular.js/commit/aef098006302689d2d75673be828e31903ee7c3c), [#2762](https://github.com/angular/angular.js/issues/2762)) - fix parameter handling on search() ([705c9d95](https://github.com/angular/angular.js/commit/705c9d95bc3157547ac6008d2f0a6a0c0e0ca60a)) - **$parse:** - unwrap promise when setting a field ([61906d35](https://github.com/angular/angular.js/commit/61906d3517428b6d52d3284b8d26d1a46e01dad7), [#1827](https://github.com/angular/angular.js/issues/1827)) - disallow access to Function constructor ([5349b200](https://github.com/angular/angular.js/commit/5349b20097dc5cdff0216ee219ac5f6e6ef8c219)) - **$q:** call `reject()` even if `$exceptionHandler` rethrows ([664526d6](https://github.com/angular/angular.js/commit/664526d69c927370c93a06745ca38de7cd03a7be)) - **$resource:** check whether response matches action.isArray ([a644ca7b](https://github.com/angular/angular.js/commit/a644ca7b4e6ba84a467bcabed8f99386eda7fb14), [#2255](https://github.com/angular/angular.js/issues/2255)) - **$sanitize:** match URI schemes case-insensitively ([7fef06fe](https://github.com/angular/angular.js/commit/7fef06fef9b6af4436f9fed10bd29d0a63707614), [#3210](https://github.com/angular/angular.js/issues/3210)) - **Scope:** - ensure that isolate scopes use the main evalAsync queue ([3967f5f7](https://github.com/angular/angular.js/commit/3967f5f7d6c8aa7b41a5352b12f457e2fbaa251a)) - watches can now be safely unregistered inside watch handlers ([8bd6619b](https://github.com/angular/angular.js/commit/8bd6619b7efa485b020fec96c76047e480469871), [#2915](https://github.com/angular/angular.js/issues/2915)) - **jqLite:** - properly detect unsupported calls for on()/off() ([3824e400](https://github.com/angular/angular.js/commit/3824e40011df1c0fdf5964d78776f1a12a29c144), [4f5dfbc3](https://github.com/angular/angular.js/commit/4f5dfbc362d9683177708ebcc00c98cf594d1287), [#3501](https://github.com/angular/angular.js/issues/3501)) - return array from multi select in val() ([306a6134](https://github.com/angular/angular.js/commit/306a613440175c7fd61d1d6eb249d1e53a46322e)) - forgive unregistration of a non-registered handler ([ab59cc6c](https://github.com/angular/angular.js/commit/ab59cc6c44705b1244a77eba999d736f9eb3c6ae)) - support space-separated events in off ([bdd4e982](https://github.com/angular/angular.js/commit/bdd4e982b7fee9811b40b545c21a74711686875c), [#3256](https://github.com/angular/angular.js/issues/3256)) - prepend array in correct order ([fd87eb0c](https://github.com/angular/angular.js/commit/fd87eb0ca5e14f213d8b31280d444dbc29c20c50)) - allow override of jqLite.triggerHandler event object ([0cac8729](https://github.com/angular/angular.js/commit/0cac8729fb3824ebb07cee84ef78b43900c7e75d)) - added optional name arg in removeData ([e1a050e6](https://github.com/angular/angular.js/commit/e1a050e6b26aca4d0e6e7125d3f6c1c8fc1d92cb)) - correctly monkey-patch core jQuery methods ([da5f537c](https://github.com/angular/angular.js/commit/da5f537ccdb0a7b4155f13f7a70ca7981ad6f689)) - **i18n:** Do not transform arrays into objects ([b3d7a038](https://github.com/angular/angular.js/commit/b3d7a038d774d823ef861b76fb8bfa22e60a3df5)) - **ngMobile/ngTouch:** - emit click event for touchy clicks ([fb7d891d](https://github.com/angular/angular.js/commit/fb7d891dacdcb9f799061d5fbb96cdd2dd912196), [#3219](https://github.com/angular/angular.js/issues/3219), [#3218](https://github.com/angular/angular.js/issues/3218), [#3137](https://github.com/angular/angular.js/issues/3137)) - prevent ngClick when item disabled ([e0340243](https://github.com/angular/angular.js/commit/e03402433d2524fd3a74bbfce984f843794996ce), [#3124](https://github.com/angular/angular.js/issues/3124), [#3132](https://github.com/angular/angular.js/issues/3132)) - ngClick should prevent unwanted opening of the soft keyboard ([0bbd20f2](https://github.com/angular/angular.js/commit/0bbd20f255b2954b5c41617fe718cf6eca36a972)) - **ngMock:** - keep withCredentials on passThrough ([3079a6f4](https://github.com/angular/angular.js/commit/3079a6f4e097a777414b8c3a8a87b8e1e20b55b5)) - keep mock.$log the api in sync with $log ([f274c0a6](https://github.com/angular/angular.js/commit/f274c0a66b28711d3b9cc7b0775e97755dd971e8), [#2343](https://github.com/angular/angular.js/issues/2343)) - **ngScenario:** select().option(val) should prefer exact value match ([22a9b1ac](https://github.com/angular/angular.js/commit/22a9b1ac07f98d07e1e5d71ce961411b5fa9b42d), [#2856](https://github.com/angular/angular.js/issues/2856)) - **Directives:** - **ngRepeat:** - handle iteration over identical obj values ([47a2a982](https://github.com/angular/angular.js/commit/47a2a9829f0a847bbee61cd142c43000d73ea98b), [#2787](https://github.com/angular/angular.js/issues/2787), [#2806](https://github.com/angular/angular.js/issues/2806)) - support growing over multi-element groups ([4953b497](https://github.com/angular/angular.js/commit/4953b49761a791d9ea74bcbe78769fec15d91083)) - **ngShowHide:** change the .ng-hide CSS class to use an !important flag ([246c1439](https://github.com/angular/angular.js/commit/246c1439b502b06823650505cbe4a3848b6fa5a3)) - **ngSubmit:** expose $event to ngSubmit callback ([3371fc25](https://github.com/angular/angular.js/commit/3371fc254a9698eae35bb6f8f1ee9c434ae761e2)) - **ngValue:** made ngValue to write value attribute to element ([09a1e7af](https://github.com/angular/angular.js/commit/09a1e7af129880cab89a2f709f22a7286f52371e)) - **ngView:** ensure ngView is terminal and uses its own manual transclusion system ([87405e25](https://github.com/angular/angular.js/commit/87405e25ae935eefd673e70ffd6144a5f455b662)) - **ngCloak:** hide ngCloak-ed element even when CSS 'display' is set ([3ffddad1](https://github.com/angular/angular.js/commit/3ffddad100e993403d13137387d0685466b46b2b)) - **`input[email]`:** fix the email regex to accept TLDs up to 6 characters long ([af731354](https://github.com/angular/angular.js/commit/af731354b0b600f87f15e1573e64a7f7acc70f3d)) - **form:** pick the right attribute name for ngForm ([0fcd1e3b](https://github.com/angular/angular.js/commit/0fcd1e3b1fa6244d02f08631d9ef81bf79996fab), [#2997](https://github.com/angular/angular.js/issues/2997)) - **select:** don't support binding to `select[multiple]` ([d87fa004](https://github.com/angular/angular.js/commit/d87fa0042375b025b98c40bff05e5f42c00af114), [#3230](https://github.com/angular/angular.js/issues/3230)) - **Filters:** - **numberFilter:** always convert scientific notation to decimal ([a13c01a8](https://github.com/angular/angular.js/commit/a13c01a8e48ea4a0d59394eb94f1b12c50cfef61)) - **Misc:** - detect transition/animation on older Android browsers ([ef5bc6c7](https://github.com/angular/angular.js/commit/ef5bc6c7c3336a64bae64fe9739cb1789907c906)) - handle duplicate params in parseKeyValue/toKeyValue ([80739409](https://github.com/angular/angular.js/commit/807394095b991357225a03d5fed81fea5c9a1abe)) - don't crash on invalid query parameters ([8264d080](https://github.com/angular/angular.js/commit/8264d08085adc2ab57f6598b9fc9f6e263c8b4f3)) - change angular.copy to correctly clone RegExp ([f80730f4](https://github.com/angular/angular.js/commit/f80730f497cb1ecb78a814f01df79b69223ad633), [#3473](https://github.com/angular/angular.js/issues/3473), [#3474](https://github.com/angular/angular.js/issues/3474)) - angular.equals now supports for regular expressions ([724819e3](https://github.com/angular/angular.js/commit/724819e3cfd8aeda1f724fb527db2b57494be9b7), [#2685](https://github.com/angular/angular.js/issues/2685)) - angular.equals should not match keys defined in the prototype chain ([7829c50f](https://github.com/angular/angular.js/commit/7829c50f9e89e779980f6d60a397aedfc7eaec61)) - angular.equals should not consider {} and [] to be equivalent ([1dcafd18](https://github.com/angular/angular.js/commit/1dcafd18afed4465ee13db91cedc8fecc3aa2c96)) - angular.bootstrap should throw an error when bootstrapping a bootstrapped element ([3ee744cc](https://github.com/angular/angular.js/commit/3ee744cc63a24b127d6a5f632934bb6ed2de275a)) - angular.toJson should skip JSON.stringify for undefined ([5a294c86](https://github.com/angular/angular.js/commit/5a294c8646452d6e49339d145faeae4f31dcd0fc)) - change css wrapping in grunt to prepend styles to the top of the head tag ([fbad068a](https://github.com/angular/angular.js/commit/fbad068aeb229fd3dd2a3004879584c728fed735)) ## Breaking Changes - **ngAnimate:** due to [81923f1e](https://github.com/angular/angular.js/commit/81923f1e41560327f7de6e8fddfda0d2612658f3), too many things changed, we'll write up a separate doc with migration instructions and will publish it at . Please check out the [ngAnimate module docs](http://ci.angularjs.org/job/angular.js-angular-master/lastSuccessfulBuild/artifact/build/docs/api/ngAnimate) and [$animate api docs](http://ci.angularjs.org/job/angular.js-angular-master/lastSuccessfulBuild/artifact/build/docs/api/ng.$animate) in the meantime. - **$compile:** - due to [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d61286840177607edd552a9df97) and [3e39ac7e](https://github.com/angular/angular.js/commit/3e39ac7e1b10d4812a44dad2f959a93361cd823b), `img[src]` URLs are now being sanitized and a whitelist configured via `$compileProvider` can be used to configure what safe urls look like. By default all common protocol prefixes are whitelisted including `data:` URIs with mime types `image/*`. Therefore this change is expected to have no impact on apps that don't contain malicious image links. - due to [38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262bb43f26d47245f6c2739), binding more than a single expression to `*[src]` or `*[ng-src]` with the exception of `` and `` elements is not supported. Concatenating expressions makes it hard to understand whether some combination of concatenated values are unsafe to use and potentially subject to XSS vulnerabilities. To simplify the task of auditing for XSS issues, we now require that a single expression be used for `*[src/ng-src]` bindings such as bindings for `iframe[src]`, `object[src]`, etc. (but not `img[src/ng-src]` since that value is sanitized). This change ensures that the possible pool of values that are used for data-binding is easier to trace down. To migrate your code, follow the example below: Before: JS: scope.baseUrl = 'page'; scope.a = 1; scope.b = 2; HTML: ')($rootScope); $rootScope.testUrl = "different_page"; $rootScope.$apply(); expect(element.attr('src')).toEqual('different_page'); })); it('should clear out src attributes for a different domain', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = "http://a.different.domain.example.com"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "http://a.different.domain.example.com"); })); it('should clear out JS src attributes', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = "javascript:alert(1);"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "javascript:alert(1);"); })); it('should clear out non-resource_url src attributes', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()"); expect($rootScope.$apply).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()"); })); it('should pass through $sce.trustAs() values in src attributes', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()"); $rootScope.$apply(); expect(element.attr('src')).toEqual('javascript:doTrustedStuff()'); })); }); describe('form[action]', function() { it('should pass through action attribute for the same domain', inject(function($compile, $rootScope, $sce) { element = $compile('
')($rootScope); $rootScope.testUrl = "different_page"; $rootScope.$apply(); expect(element.attr('action')).toEqual('different_page'); })); it('should clear out action attribute for a different domain', inject(function($compile, $rootScope, $sce) { element = $compile('
')($rootScope); $rootScope.testUrl = "http://a.different.domain.example.com"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "http://a.different.domain.example.com"); })); it('should clear out JS action attribute', inject(function($compile, $rootScope, $sce) { element = $compile('
')($rootScope); $rootScope.testUrl = "javascript:alert(1);"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "javascript:alert(1);"); })); it('should clear out non-resource_url action attribute', inject(function($compile, $rootScope, $sce) { element = $compile('
')($rootScope); $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()"); expect($rootScope.$apply).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()"); })); it('should pass through $sce.trustAs() values in action attribute', inject(function($compile, $rootScope, $sce) { element = $compile('
')($rootScope); $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()"); $rootScope.$apply(); expect(element.attr('action')).toEqual('javascript:doTrustedStuff()'); })); }); if (!msie || msie >= 11) { describe('iframe[srcdoc]', function() { it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.html = '
hello
'; expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp( /Can't interpolate: {{html}}\n/.source + /[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source)); })); it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) { element = $compile('')($rootScope); $rootScope.html = $sce.trustAsCss('
hello
'); expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp( /Can't interpolate: {{html}}\n/.source + /[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source)); })); it('should set html for trusted values', inject(function($rootScope, $compile, $sce) { element = $compile('')($rootScope); $rootScope.html = $sce.trustAsHtml('
hello
'); $rootScope.$digest(); expect(angular.lowercase(element.attr('srcdoc'))).toEqual('
hello
'); })); }); } describe('ngAttr* attribute binding', function() { it('should bind after digest but not before', inject(function($compile, $rootScope) { $rootScope.name = "Misko"; element = $compile('')($rootScope); expect(element.attr('test')).toBeUndefined(); $rootScope.$digest(); expect(element.attr('test')).toBe('Misko'); })); it('should work with different prefixes', inject(function($compile, $rootScope) { $rootScope.name = "Misko"; element = $compile('')($rootScope); expect(element.attr('test')).toBeUndefined(); expect(element.attr('test2')).toBeUndefined(); expect(element.attr('test3')).toBeUndefined(); $rootScope.$digest(); expect(element.attr('test')).toBe('Misko'); expect(element.attr('test2')).toBe('Misko'); expect(element.attr('test3')).toBe('Misko'); })); it('should work with the "href" attribute', inject(function($compile, $rootScope) { $rootScope.value = 'test'; element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.attr('href')).toBe('test/test'); })); it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) { $rootScope.name = "Misko"; element = $compile('')($rootScope); expect(element.attr('test2')).toBeUndefined(); expect(element.attr('test3')).toBeUndefined(); expect(element.attr('test4')).toBeUndefined(); $rootScope.$digest(); expect(element.attr('test2')).toBe('Misko'); expect(element.attr('test3')).toBe('Misko'); expect(element.attr('test4')).toBe('Misko'); })); describe('when an attribute has a dash-separated name', function () { it('should work with different prefixes', inject(function($compile, $rootScope) { $rootScope.name = "JamieMason"; element = $compile('')($rootScope); expect(element.attr('dash-test')).toBeUndefined(); expect(element.attr('dash-test2')).toBeUndefined(); expect(element.attr('dash-test3')).toBeUndefined(); $rootScope.$digest(); expect(element.attr('dash-test')).toBe('JamieMason'); expect(element.attr('dash-test2')).toBe('JamieMason'); expect(element.attr('dash-test3')).toBe('JamieMason'); })); it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) { $rootScope.name = "JamieMason"; element = $compile('')($rootScope); expect(element.attr('dash-test2')).toBeUndefined(); expect(element.attr('dash-test3')).toBeUndefined(); expect(element.attr('dash-test4')).toBeUndefined(); $rootScope.$digest(); expect(element.attr('dash-test2')).toBe('JamieMason'); expect(element.attr('dash-test3')).toBe('JamieMason'); expect(element.attr('dash-test4')).toBe('JamieMason'); })); }); }); describe('multi-element directive', function() { it('should group on link function', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '' + '' + '
')($rootScope); $rootScope.$digest(); var spans = element.find('span'); expect(spans.eq(0)).toBeHidden(); expect(spans.eq(1)).toBeHidden(); })); it('should group on compile function', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '{{i}}A' + '{{i}}B;' + '
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('1A1B;2A2B;'); })); it('should support grouping over text nodes', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '{{i}}A' + ':' + // Important: proves that we can iterate over non-elements '{{i}}B;' + '
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('1A:1B;2A:2B;'); })); it('should group on $root compile function', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '{{i}}A' + '{{i}}B;' + '
')($rootScope); $rootScope.$digest(); element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level. expect(element.text()).toEqual('1A1B;2A2B;'); })); it('should group on nested groups of same directive', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '
{{i}}A
' + '' + '' + '
{{i}}B;
' + '
')($rootScope); $rootScope.$digest(); element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level. expect(element.text()).toEqual('1A..1B;2A..2B;'); })); it('should group on nested groups', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '
{{i}}(
' + '{{j}}-' + '{{j}}' + '
){{i}};
' + '
')($rootScope); $rootScope.$digest(); element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level. expect(element.text()).toEqual('1(2-23-3)1;2(2-23-3)2;'); })); it('should throw error if unterminated', function () { module(function($compileProvider) { $compileProvider.directive('foo', function() { return { }; }); }); inject(function($compile, $rootScope) { expect(function() { element = $compile( '
' + '' + '
'); }).toThrowMinErr("$compile", "uterdir", "Unterminated attribute, found 'foo-start' but no matching 'foo-end' found."); }); }); it('should correctly collect ranges on multiple directives on a single element', function () { module(function($compileProvider) { $compileProvider.directive('emptyDirective', function() { return function (scope, element) { element.data('x', 'abc'); }; }); $compileProvider.directive('rangeDirective', function() { return { link: function (scope) { scope.x = 'X'; scope.y = 'Y'; } }; }); }); inject(function ($compile, $rootScope) { element = $compile( '
' + '
{{x}}
' + '
{{y}}
' + '
' )($rootScope); $rootScope.$digest(); expect(element.text()).toBe('XY'); expect(angular.element(element[0].firstChild).data('x')).toBe('abc'); }); }); it('should throw error if unterminated (containing termination as a child)', function () { module(function($compileProvider) { $compileProvider.directive('foo', function() { return { }; }); }); inject(function($compile) { expect(function() { element = $compile( '
' + '' + '
'); }).toThrowMinErr("$compile", "uterdir", "Unterminated attribute, found 'foo-start' but no matching 'foo-end' found."); }); }); it('should support data- and x- prefix', inject(function($compile, $rootScope) { $rootScope.show = false; element = $compile( '
' + '' + '' + '' + '' + '
')($rootScope); $rootScope.$digest(); var spans = element.find('span'); expect(spans.eq(0)).toBeHidden(); expect(spans.eq(1)).toBeHidden(); expect(spans.eq(2)).toBeHidden(); expect(spans.eq(3)).toBeHidden(); })); }); describe('$animate animation hooks', function() { beforeEach(module('mock.animate')); it('should automatically fire the addClass and removeClass animation hooks', inject(function($compile, $animate, $rootScope) { var data, element = jqLite('
'); $compile(element)($rootScope); $rootScope.$digest(); expect(element.hasClass('fire')).toBe(true); $rootScope.val1 = 'ice'; $rootScope.val2 = 'rice'; $rootScope.$digest(); data = $animate.flushNext('addClass'); expect(data.params[1]).toBe('ice rice'); expect(element.hasClass('ice')).toBe(true); expect(element.hasClass('rice')).toBe(true); expect(element.hasClass('fire')).toBe(true); $rootScope.val2 = 'dice'; $rootScope.$digest(); data = $animate.flushNext('removeClass'); expect(data.params[1]).toBe('rice'); data = $animate.flushNext('addClass'); expect(data.params[1]).toBe('dice'); expect(element.hasClass('ice')).toBe(true); expect(element.hasClass('dice')).toBe(true); expect(element.hasClass('fire')).toBe(true); $rootScope.val1 = ''; $rootScope.val2 = ''; $rootScope.$digest(); data = $animate.flushNext('removeClass'); expect(data.params[1]).toBe('ice dice'); expect(element.hasClass('ice')).toBe(false); expect(element.hasClass('dice')).toBe(false); expect(element.hasClass('fire')).toBe(true); })); }); }); angular.js-1.2.11/test/ng/controllerSpec.js000066400000000000000000000077341227375216300206020ustar00rootroot00000000000000'use strict'; describe('$controller', function() { var $controllerProvider, $controller; beforeEach(module(function(_$controllerProvider_) { $controllerProvider = _$controllerProvider_; })); beforeEach(inject(function(_$controller_) { $controller = _$controller_; })); describe('provider', function() { it('should allow registration of controllers', function() { var FooCtrl = function($scope) { $scope.foo = 'bar' }, scope = {}, ctrl; $controllerProvider.register('FooCtrl', FooCtrl); ctrl = $controller('FooCtrl', {$scope: scope}); expect(scope.foo).toBe('bar'); expect(ctrl instanceof FooCtrl).toBe(true); }); it('should allow registration of map of controllers', function() { var FooCtrl = function($scope) { $scope.foo = 'foo' }, BarCtrl = function($scope) { $scope.bar = 'bar' }, scope = {}, ctrl; $controllerProvider.register({FooCtrl: FooCtrl, BarCtrl: BarCtrl} ); ctrl = $controller('FooCtrl', {$scope: scope}); expect(scope.foo).toBe('foo'); expect(ctrl instanceof FooCtrl).toBe(true); ctrl = $controller('BarCtrl', {$scope: scope}); expect(scope.bar).toBe('bar'); expect(ctrl instanceof BarCtrl).toBe(true); }); it('should allow registration of controllers annotated with arrays', function() { var FooCtrl = function($scope) { $scope.foo = 'bar' }, scope = {}, ctrl; $controllerProvider.register('FooCtrl', ['$scope', FooCtrl]); ctrl = $controller('FooCtrl', {$scope: scope}); expect(scope.foo).toBe('bar'); expect(ctrl instanceof FooCtrl).toBe(true); }); it('should throw an exception if a controller is called "hasOwnProperty"', function () { expect(function() { $controllerProvider.register('hasOwnProperty', function($scope) {}); }).toThrowMinErr('ng', 'badname', "hasOwnProperty is not a valid controller name"); }); }); it('should return instance of given controller class', function() { var MyClass = function() {}, ctrl = $controller(MyClass); expect(ctrl).toBeDefined(); expect(ctrl instanceof MyClass).toBe(true); }); it('should inject arguments', inject(function($http) { var MyClass = function($http) { this.$http = $http; }; var ctrl = $controller(MyClass); expect(ctrl.$http).toBe($http); })); it('should inject given scope', function() { var MyClass = function($scope) { this.$scope = $scope; }; var scope = {}, ctrl = $controller(MyClass, {$scope: scope}); expect(ctrl.$scope).toBe(scope); }); it('should instantiate controller defined on window', inject(function($window) { var scope = {}; var Foo = function() {}; $window.a = {Foo: Foo}; var foo = $controller('a.Foo', {$scope: scope}); expect(foo).toBeDefined(); expect(foo instanceof Foo).toBe(true); })); describe('ctrl as syntax', function() { it('should publish controller instance into scope', function() { var scope = {}; $controllerProvider.register('FooCtrl', function() { this.mark = 'foo'; }); var foo = $controller('FooCtrl as foo', {$scope: scope}); expect(scope.foo).toBe(foo); expect(scope.foo.mark).toBe('foo'); }); it('should allow controllers with dots', function() { var scope = {}; $controllerProvider.register('a.b.FooCtrl', function() { this.mark = 'foo'; }); var foo = $controller('a.b.FooCtrl as foo', {$scope: scope}); expect(scope.foo).toBe(foo); expect(scope.foo.mark).toBe('foo'); }); it('should throw an error if $scope is not provided', function() { $controllerProvider.register('a.b.FooCtrl', function() { this.mark = 'foo'; }); expect(function() { $controller('a.b.FooCtrl as foo'); }).toThrowMinErr("$controller", "noscp", "Cannot export controller 'a.b.FooCtrl' as 'foo'! No $scope object provided via `locals`."); }); }); }); angular.js-1.2.11/test/ng/directive/000077500000000000000000000000001227375216300172115ustar00rootroot00000000000000angular.js-1.2.11/test/ng/directive/aSpec.js000066400000000000000000000101611227375216300206010ustar00rootroot00000000000000'use strict'; describe('a', function() { var element, $compile, $rootScope; beforeEach(inject(function(_$compile_, _$rootScope_) { $compile = _$compile_; $rootScope = _$rootScope_; })); afterEach(function(){ dealoc(element); }); it('should prevent default action to be executed when href is empty', function() { var orgLocation = document.location.href, preventDefaultCalled = false, event; element = $compile('empty link')($rootScope); if (msie < 9) { event = document.createEventObject(); expect(event.returnValue).not.toBeDefined(); element[0].fireEvent('onclick', event); expect(event.returnValue).toEqual(false); } else { event = document.createEvent('MouseEvent'); event.initMouseEvent( 'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); event.preventDefaultOrg = event.preventDefault; event.preventDefault = function() { preventDefaultCalled = true; if (this.preventDefaultOrg) this.preventDefaultOrg(); }; element[0].dispatchEvent(event); expect(preventDefaultCalled).toEqual(true); } expect(document.location.href).toEqual(orgLocation); }); it('should prevent IE for changing text content when setting attribute', function() { // see issue #1949 element = jqLite('hello@you'); $compile(element); element.attr('href', 'bye@me'); expect(element.text()).toBe('hello@you'); }); it('should not link and hookup an event if href is present at compile', function() { var jq = jQuery || jqLite; element = jq('hello@you'); var linker = $compile(element); spyOn(jq.prototype, 'on'); linker($rootScope); expect(jq.prototype.on).not.toHaveBeenCalled(); }); it('should not link and hookup an event if name is present at compile', function() { var jq = jQuery || jqLite; element = jq('hello@you'); var linker = $compile(element); spyOn(jq.prototype, 'on'); linker($rootScope); expect(jq.prototype.on).not.toHaveBeenCalled(); }); if (isDefined(window.SVGElement)) { describe('SVGAElement', function() { it('should prevent default action to be executed when href is empty', function() { var orgLocation = document.location.href, preventDefaultCalled = false, event, child; element = $compile('empty link')($rootScope); child = element.children('a'); if (msie < 9) { event = document.createEventObject(); expect(event.returnValue).not.toBeDefined(); child[0].fireEvent('onclick', event); expect(event.returnValue).toEqual(false); } else { event = document.createEvent('MouseEvent'); event.initMouseEvent( 'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); event.preventDefaultOrg = event.preventDefault; event.preventDefault = function() { preventDefaultCalled = true; if (this.preventDefaultOrg) this.preventDefaultOrg(); }; child[0].dispatchEvent(event); expect(preventDefaultCalled).toEqual(true); } expect(document.location.href).toEqual(orgLocation); }); it('should not link and hookup an event if xlink:href is present at compile', function() { var jq = jQuery || jqLite; element = jq('hello@you'); var linker = $compile(element); spyOn(jq.prototype, 'on'); linker($rootScope); expect(jq.prototype.on).not.toHaveBeenCalled(); }); it('should not link and hookup an event if name is present at compile', function() { var jq = jQuery || jqLite; element = jq('hello@you'); var linker = $compile(element); spyOn(jq.prototype, 'on'); linker($rootScope); expect(jq.prototype.on).not.toHaveBeenCalled(); }); }); } }); angular.js-1.2.11/test/ng/directive/booleanAttrsSpec.js000066400000000000000000000210011227375216300230110ustar00rootroot00000000000000'use strict'; describe('boolean attr directives', function() { var element; afterEach(function() { dealoc(element); }); it('should properly evaluate 0 as false', inject(function($rootScope, $compile) { // jQuery does not treat 0 as false, when setting attr() element = $compile('')($rootScope) $rootScope.isDisabled = 0; $rootScope.$digest(); expect(element.attr('disabled')).toBeFalsy(); $rootScope.isDisabled = 1; $rootScope.$digest(); expect(element.attr('disabled')).toBeTruthy(); })); it('should bind disabled', inject(function($rootScope, $compile) { element = $compile('')($rootScope) $rootScope.isDisabled = false; $rootScope.$digest(); expect(element.attr('disabled')).toBeFalsy(); $rootScope.isDisabled = true; $rootScope.$digest(); expect(element.attr('disabled')).toBeTruthy(); })); it('should bind checked', inject(function($rootScope, $compile) { element = $compile('')($rootScope) $rootScope.isChecked = false; $rootScope.$digest(); expect(element.attr('checked')).toBeFalsy(); $rootScope.isChecked=true; $rootScope.$digest(); expect(element.attr('checked')).toBeTruthy(); })); it('should bind selected', inject(function($rootScope, $compile) { element = $compile('')($rootScope) jqLite(document.body).append(element) $rootScope.isSelected=false; $rootScope.$digest(); expect(element.children()[1].selected).toBeFalsy(); $rootScope.isSelected=true; $rootScope.$digest(); expect(element.children()[1].selected).toBeTruthy(); })); it('should bind readonly', inject(function($rootScope, $compile) { element = $compile('')($rootScope) $rootScope.isReadonly=false; $rootScope.$digest(); expect(element.attr('readOnly')).toBeFalsy(); $rootScope.isReadonly=true; $rootScope.$digest(); expect(element.attr('readOnly')).toBeTruthy(); })); it('should bind open', inject(function($rootScope, $compile) { element = $compile('
')($rootScope) $rootScope.isOpen=false; $rootScope.$digest(); expect(element.attr('open')).toBeFalsy(); $rootScope.isOpen=true; $rootScope.$digest(); expect(element.attr('open')).toBeTruthy(); })); describe('multiple', function() { it('should NOT bind to multiple via ngMultiple', inject(function($rootScope, $compile) { element = $compile('')($rootScope) $rootScope.isMultiple=false; $rootScope.$digest(); expect(element.attr('multiple')).toBeFalsy(); $rootScope.isMultiple='multiple'; $rootScope.$digest(); expect(element.attr('multiple')).toBeFalsy(); // ignore })); it('should throw an exception if binding to multiple attribute', inject(function($rootScope, $compile) { if (msie < 9) return; //IE8 doesn't support biding to boolean attributes expect(function() { $compile('') }).toThrowMinErr('$compile', 'selmulti', 'Binding to the \'multiple\' attribute is not supported. ' + 'Element: ' + '')(scope); scope.inputPresent = true; scope.$digest(); var form = scope.myForm; control.$setValidity('required', false); expect(form.alias).toBe(control); expect(form.$error.required).toEqual([control]); // remove nested control scope.inputPresent = false; scope.$apply(); expect(form.$error.required).toBe(false); expect(form.alias).toBeUndefined(); }); it('should use ngForm value as form name', function() { doc = $compile( '
' + '' + '
')(scope); expect(scope.myForm).toBeDefined(); expect(scope.myForm.alias).toBeDefined(); }); it('should use ngForm value as form name when nested inside form', function () { doc = $compile( '
' + '
' + '
')(scope); expect(scope.myForm).toBeDefined(); expect(scope.myForm.nestedForm).toBeDefined(); expect(scope.myForm.nestedForm.alias).toBeDefined(); }); it('should publish form to scope when name attr is defined', function() { doc = $compile('
')(scope); expect(scope.myForm).toBeTruthy(); expect(doc.data('$formController')).toBeTruthy(); expect(doc.data('$formController')).toEqual(scope.myForm); }); it('should support expression in form name', function() { doc = $compile('
')(scope); expect(scope.obj).toBeDefined(); expect(scope.obj.myForm).toBeTruthy(); }); it('should support two forms on a single scope', function() { doc = $compile( '
' + '
' + '' + '
' + '
' + '' + '
' + '
' )(scope); scope.$apply(); expect(scope.formA.$error.required.length).toBe(1); expect(scope.formA.$error.required).toEqual([scope.formA.firstName]); expect(scope.formB.$error.required.length).toBe(1); expect(scope.formB.$error.required).toEqual([scope.formB.lastName]); var inputA = doc.find('input').eq(0), inputB = doc.find('input').eq(1); changeInputValue(inputA, 'val1'); changeInputValue(inputB, 'val2'); expect(scope.firstName).toBe('val1'); expect(scope.lastName).toBe('val2'); expect(scope.formA.$error.required).toBe(false); expect(scope.formB.$error.required).toBe(false); }); it('should publish widgets', function() { doc = jqLite('
'); $compile(doc)(scope); var widget = scope.form.w1; expect(widget).toBeDefined(); expect(widget.$pristine).toBe(true); expect(widget.$dirty).toBe(false); expect(widget.$valid).toBe(true); expect(widget.$invalid).toBe(false); }); it('should throw an exception if an input has name="hasOwnProperty"', function() { doc = jqLite( '
'+ ''+ ''+ '
'); expect(function() { $compile(doc)(scope); }).toThrowMinErr('ng', 'badname'); }); describe('preventing default submission', function() { it('should prevent form submission', function() { var nextTurn = false, submitted = false, reloadPrevented; doc = jqLite('
' + '' + '
'); var assertPreventDefaultListener = function(e) { reloadPrevented = e.defaultPrevented || (e.returnValue === false); }; // native dom event listeners in IE8 fire in LIFO order so we have to register them // there in different order than in other browsers if (msie==8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener); $compile(doc)(scope); scope.submitMe = function() { submitted = true; } if (msie!=8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener); browserTrigger(doc.find('input')); // let the browser process all events (and potentially reload the page) setTimeout(function() { nextTurn = true;}); waitsFor(function() { return nextTurn; }); runs(function() { expect(reloadPrevented).toBe(true); expect(submitted).toBe(true); // prevent mem leak in test removeEventListenerFn(doc[0], 'submit', assertPreventDefaultListener); }); }); it('should prevent the default when the form is destroyed by a submission via a click event', inject(function($timeout) { doc = jqLite('
' + '
' + '' + '
' + '
'); var form = doc.find('form'), destroyed = false, nextTurn = false, submitted = false, reloadPrevented; scope.destroy = function() { // yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep // this test small. Imagine that the destroy action will cause a model change (e.g. // $location change) that will cause some directive to destroy the dom (e.g. ngView+$route) doc.empty(); destroyed = true; } scope.submitMe = function() { submitted = true; } var assertPreventDefaultListener = function(e) { reloadPrevented = e.defaultPrevented || (e.returnValue === false); }; // native dom event listeners in IE8 fire in LIFO order so we have to register them // there in different order than in other browsers if (msie == 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener); $compile(doc)(scope); if (msie != 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener); browserTrigger(doc.find('button'), 'click'); // let the browser process all events (and potentially reload the page) setTimeout(function() { nextTurn = true;}, 100); waitsFor(function() { return nextTurn; }); // I can't get IE8 to automatically trigger submit in this test, in production it does it // properly if (msie == 8) browserTrigger(form, 'submit'); runs(function() { expect(doc.html()).toBe(''); expect(destroyed).toBe(true); expect(submitted).toBe(false); // this is known corner-case that is not currently handled // the issue is that the submit listener is destroyed before // the event propagates there. we can fix this if we see // the issue in the wild, I'm not going to bother to do it // now. (i) // IE9 and IE10 are special and don't fire submit event when form was destroyed if (msie < 9) { expect(reloadPrevented).toBe(true); $timeout.flush(); } // prevent mem leak in test removeEventListenerFn(form[0], 'submit', assertPreventDefaultListener); }); })); it('should NOT prevent form submission if action attribute present', function() { var callback = jasmine.createSpy('submit').andCallFake(function(event) { expect(event.isDefaultPrevented()).toBe(false); event.preventDefault(); }); doc = $compile('
')(scope); doc.on('submit', callback); browserTrigger(doc, 'submit'); expect(callback).toHaveBeenCalledOnce(); }); }); describe('nested forms', function() { it('should chain nested forms', function() { doc = jqLite( '' + '' + '' + '' + '' + ''); $compile(doc)(scope); var parent = scope.parent, child = scope.child, inputA = child.inputA, inputB = child.inputB; inputA.$setValidity('MyError', false); inputB.$setValidity('MyError', false); expect(parent.$error.MyError).toEqual([child]); expect(child.$error.MyError).toEqual([inputA, inputB]); inputA.$setValidity('MyError', true); expect(parent.$error.MyError).toEqual([child]); expect(child.$error.MyError).toEqual([inputB]); inputB.$setValidity('MyError', true); expect(parent.$error.MyError).toBe(false); expect(child.$error.MyError).toBe(false); child.$setDirty(); expect(parent.$dirty).toBeTruthy(); }); it('should deregister a child form when its DOM is removed', function() { doc = jqLite( '
' + '
' + '' + '
' + '
'); $compile(doc)(scope); scope.$apply(); var parent = scope.parent, child = scope.child; expect(parent).toBeDefined(); expect(child).toBeDefined(); expect(parent.$error.required).toEqual([child]); doc.children().remove(); //remove child expect(parent.child).toBeUndefined(); expect(scope.child).toBeUndefined(); expect(parent.$error.required).toBe(false); }); it('should deregister a child form whose name is an expression when its DOM is removed', function() { doc = jqLite( '
' + '
' + '' + '
' + '
'); $compile(doc)(scope); scope.$apply(); var parent = scope.parent, child = scope.child.form; expect(parent).toBeDefined(); expect(child).toBeDefined(); expect(parent.$error.required).toEqual([child]); doc.children().remove(); //remove child expect(parent.child).toBeUndefined(); expect(scope.child.form).toBeUndefined(); expect(parent.$error.required).toBe(false); }); it('should deregister a input when it is removed from DOM', function() { doc = jqLite( '
' + '
' + '' + '
' + '
'); $compile(doc)(scope); scope.inputPresent = true; scope.$apply(); var parent = scope.parent, child = scope.child, input = child.inputA; expect(parent).toBeDefined(); expect(child).toBeDefined(); expect(parent.$error.required).toEqual([child]); expect(child.$error.required).toEqual([input]); expect(doc.hasClass('ng-invalid')).toBe(true); expect(doc.hasClass('ng-invalid-required')).toBe(true); expect(doc.find('div').hasClass('ng-invalid')).toBe(true); expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true); //remove child input scope.inputPresent = false; scope.$apply(); expect(parent.$error.required).toBe(false); expect(child.$error.required).toBe(false); expect(doc.hasClass('ng-valid')).toBe(true); expect(doc.hasClass('ng-valid-required')).toBe(true); expect(doc.find('div').hasClass('ng-valid')).toBe(true); expect(doc.find('div').hasClass('ng-valid-required')).toBe(true); }); it('should chain nested forms in repeater', function() { doc = jqLite( '' + '' + '' + '' + ''); $compile(doc)(scope); scope.$apply(function() { scope.forms = [1]; }); var parent = scope.parent; var child = doc.find('input').scope().child; var input = child.text; expect(parent).toBeDefined(); expect(child).toBeDefined(); expect(input).toBeDefined(); input.$setValidity('myRule', false); expect(input.$error.myRule).toEqual(true); expect(child.$error.myRule).toEqual([input]); expect(parent.$error.myRule).toEqual([child]); input.$setValidity('myRule', true); expect(parent.$error.myRule).toBe(false); expect(child.$error.myRule).toBe(false); }); }) describe('validation', function() { beforeEach(function() { doc = $compile( '
' + '' + '
')(scope); scope.$digest(); }); it('should have ng-valid/ng-invalid css class', function() { expect(doc).toBeValid(); control.$setValidity('error', false); expect(doc).toBeInvalid(); expect(doc.hasClass('ng-valid-error')).toBe(false); expect(doc.hasClass('ng-invalid-error')).toBe(true); control.$setValidity('another', false); expect(doc.hasClass('ng-valid-error')).toBe(false); expect(doc.hasClass('ng-invalid-error')).toBe(true); expect(doc.hasClass('ng-valid-another')).toBe(false); expect(doc.hasClass('ng-invalid-another')).toBe(true); control.$setValidity('error', true); expect(doc).toBeInvalid(); expect(doc.hasClass('ng-valid-error')).toBe(true); expect(doc.hasClass('ng-invalid-error')).toBe(false); expect(doc.hasClass('ng-valid-another')).toBe(false); expect(doc.hasClass('ng-invalid-another')).toBe(true); control.$setValidity('another', true); expect(doc).toBeValid(); expect(doc.hasClass('ng-valid-error')).toBe(true); expect(doc.hasClass('ng-invalid-error')).toBe(false); expect(doc.hasClass('ng-valid-another')).toBe(true); expect(doc.hasClass('ng-invalid-another')).toBe(false); }); it('should have ng-pristine/ng-dirty css class', function() { expect(doc).toBePristine(); control.$setViewValue(''); scope.$apply(); expect(doc).toBeDirty(); }); }); describe('$setPristine', function() { it('should reset pristine state of form and controls', function() { doc = $compile( '
' + '' + '' + '
')(scope); scope.$digest(); var form = doc, formCtrl = scope.testForm, input1 = form.find('input').eq(0), input1Ctrl = input1.controller('ngModel'), input2 = form.find('input').eq(1), input2Ctrl = input2.controller('ngModel'); input1Ctrl.$setViewValue('xx'); input2Ctrl.$setViewValue('yy'); scope.$apply(); expect(form).toBeDirty(); expect(input1).toBeDirty(); expect(input2).toBeDirty(); formCtrl.$setPristine(); expect(form).toBePristine(); expect(formCtrl.$pristine).toBe(true); expect(formCtrl.$dirty).toBe(false); expect(input1).toBePristine(); expect(input1Ctrl.$pristine).toBe(true); expect(input1Ctrl.$dirty).toBe(false); expect(input2).toBePristine(); expect(input2Ctrl.$pristine).toBe(true); expect(input2Ctrl.$dirty).toBe(false); }); it('should reset pristine state of anonymous form controls', function() { doc = $compile( '
' + '' + '
')(scope); scope.$digest(); var form = doc, formCtrl = scope.testForm, input = form.find('input').eq(0), inputCtrl = input.controller('ngModel'); inputCtrl.$setViewValue('xx'); scope.$apply(); expect(form).toBeDirty(); expect(input).toBeDirty(); formCtrl.$setPristine(); expect(form).toBePristine(); expect(formCtrl.$pristine).toBe(true); expect(formCtrl.$dirty).toBe(false); expect(input).toBePristine(); expect(inputCtrl.$pristine).toBe(true); expect(inputCtrl.$dirty).toBe(false); }); it('should reset pristine state of nested forms', function() { doc = $compile( '
' + '
' + '' + '
' + '
')(scope); scope.$digest(); var form = doc, formCtrl = scope.testForm, nestedForm = form.find('div'), nestedFormCtrl = nestedForm.controller('form'), nestedInput = form.find('input').eq(0), nestedInputCtrl = nestedInput.controller('ngModel'); nestedInputCtrl.$setViewValue('xx'); scope.$apply(); expect(form).toBeDirty(); expect(nestedForm).toBeDirty(); expect(nestedInput).toBeDirty(); formCtrl.$setPristine(); expect(form).toBePristine(); expect(formCtrl.$pristine).toBe(true); expect(formCtrl.$dirty).toBe(false); expect(nestedForm).toBePristine(); expect(nestedFormCtrl.$pristine).toBe(true); expect(nestedFormCtrl.$dirty).toBe(false); expect(nestedInput).toBePristine(); expect(nestedInputCtrl.$pristine).toBe(true); expect(nestedInputCtrl.$dirty).toBe(false); }); }); }); angular.js-1.2.11/test/ng/directive/inputSpec.js000066400000000000000000001247111227375216300215270ustar00rootroot00000000000000'use strict'; describe('NgModelController', function() { var ctrl, scope, ngModelAccessor, element, parentFormCtrl; beforeEach(inject(function($rootScope, $controller) { var attrs = {name: 'testAlias', ngModel: 'value'}; parentFormCtrl = { $setValidity: jasmine.createSpy('$setValidity'), $setDirty: jasmine.createSpy('$setDirty') } element = jqLite('
'); element.data('$formController', parentFormCtrl); scope = $rootScope; ngModelAccessor = jasmine.createSpy('ngModel accessor'); ctrl = $controller(NgModelController, { $scope: scope, $element: element.find('input'), $attrs: attrs }); })); afterEach(function() { dealoc(element); }); it('should fail on non-assignable model binding', inject(function($controller) { var exception; try { $controller(NgModelController, { $scope: null, $element: jqLite(''), $attrs: { ngModel: '1+2' } }); } catch (e) { exception = e; } expect(exception.message). toMatch(/^\[ngModel:nonassign\] Expression '1\+2' is non\-assignable\. Element: /); })); it('should init the properties', function() { expect(ctrl.$dirty).toBe(false); expect(ctrl.$pristine).toBe(true); expect(ctrl.$valid).toBe(true); expect(ctrl.$invalid).toBe(false); expect(ctrl.$viewValue).toBeDefined(); expect(ctrl.$modelValue).toBeDefined(); expect(ctrl.$formatters).toEqual([]); expect(ctrl.$parsers).toEqual([]); expect(ctrl.$name).toBe('testAlias'); }); describe('setValidity', function() { it('should propagate invalid to the parent form only when valid', function() { expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled(); ctrl.$setValidity('ERROR', false); expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('ERROR', false, ctrl); parentFormCtrl.$setValidity.reset(); ctrl.$setValidity('ERROR', false); expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled(); }); it('should set and unset the error', function() { ctrl.$setValidity('required', false); expect(ctrl.$error.required).toBe(true); ctrl.$setValidity('required', true); expect(ctrl.$error.required).toBe(false); }); it('should set valid/invalid', function() { ctrl.$setValidity('first', false); expect(ctrl.$valid).toBe(false); expect(ctrl.$invalid).toBe(true); ctrl.$setValidity('second', false); expect(ctrl.$valid).toBe(false); expect(ctrl.$invalid).toBe(true); ctrl.$setValidity('second', true); expect(ctrl.$valid).toBe(false); expect(ctrl.$invalid).toBe(true); ctrl.$setValidity('first', true); expect(ctrl.$valid).toBe(true); expect(ctrl.$invalid).toBe(false); }); it('should emit $valid only when $invalid', function() { ctrl.$setValidity('error', true); expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', true, ctrl); parentFormCtrl.$setValidity.reset(); ctrl.$setValidity('error', false); expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', false, ctrl); parentFormCtrl.$setValidity.reset(); ctrl.$setValidity('error', true); expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', true, ctrl); }); }); describe('setPristine', function() { it('should set control to its pristine state', function() { ctrl.$setViewValue('edit'); expect(ctrl.$dirty).toBe(true); expect(ctrl.$pristine).toBe(false); ctrl.$setPristine(); expect(ctrl.$dirty).toBe(false); expect(ctrl.$pristine).toBe(true); }); }); describe('view -> model', function() { it('should set the value to $viewValue', function() { ctrl.$setViewValue('some-val'); expect(ctrl.$viewValue).toBe('some-val'); }); it('should pipeline all registered parsers and set result to $modelValue', function() { var log = []; ctrl.$parsers.push(function(value) { log.push(value); return value + '-a'; }); ctrl.$parsers.push(function(value) { log.push(value); return value + '-b'; }); ctrl.$setViewValue('init'); expect(log).toEqual(['init', 'init-a']); expect(ctrl.$modelValue).toBe('init-a-b'); }); it('should fire viewChangeListeners when the value changes in the view (even if invalid)', function() { var spy = jasmine.createSpy('viewChangeListener'); ctrl.$viewChangeListeners.push(spy); ctrl.$setViewValue('val'); expect(spy).toHaveBeenCalledOnce(); spy.reset(); // invalid ctrl.$parsers.push(function() {return undefined;}); ctrl.$setViewValue('val'); expect(spy).toHaveBeenCalledOnce(); }); it('should reset the model when the view is invalid', function() { ctrl.$setViewValue('aaaa'); expect(ctrl.$modelValue).toBe('aaaa'); // add a validator that will make any input invalid ctrl.$parsers.push(function() {return undefined;}); expect(ctrl.$modelValue).toBe('aaaa'); ctrl.$setViewValue('bbbb'); expect(ctrl.$modelValue).toBeUndefined(); }); it('should call parentForm.$setDirty only when pristine', function() { ctrl.$setViewValue(''); expect(ctrl.$pristine).toBe(false); expect(ctrl.$dirty).toBe(true); expect(parentFormCtrl.$setDirty).toHaveBeenCalledOnce(); parentFormCtrl.$setDirty.reset(); ctrl.$setViewValue(''); expect(ctrl.$pristine).toBe(false); expect(ctrl.$dirty).toBe(true); expect(parentFormCtrl.$setDirty).not.toHaveBeenCalled(); }); }); describe('model -> view', function() { it('should set the value to $modelValue', function() { scope.$apply(function() { scope.value = 10; }); expect(ctrl.$modelValue).toBe(10); }); it('should pipeline all registered formatters in reversed order and set result to $viewValue', function() { var log = []; ctrl.$formatters.unshift(function(value) { log.push(value); return value + 2; }); ctrl.$formatters.unshift(function(value) { log.push(value); return value + ''; }); scope.$apply(function() { scope.value = 3; }); expect(log).toEqual([3, 5]); expect(ctrl.$viewValue).toBe('5'); }); it('should $render only if value changed', function() { spyOn(ctrl, '$render'); scope.$apply(function() { scope.value = 3; }); expect(ctrl.$render).toHaveBeenCalledOnce(); ctrl.$render.reset(); ctrl.$formatters.push(function() {return 3;}); scope.$apply(function() { scope.value = 5; }); expect(ctrl.$render).not.toHaveBeenCalled(); }); it('should clear the view even if invalid', function() { spyOn(ctrl, '$render'); ctrl.$formatters.push(function() {return undefined;}); scope.$apply(function() { scope.value = 5; }); expect(ctrl.$render).toHaveBeenCalledOnce(); }); }); }); describe('ngModel', function() { it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty)', inject(function($compile, $rootScope, $sniffer) { var element = $compile('')($rootScope); $rootScope.$digest(); expect(element).toBeValid(); expect(element).toBePristine(); expect(element.hasClass('ng-valid-email')).toBe(true); expect(element.hasClass('ng-invalid-email')).toBe(false); $rootScope.$apply(function() { $rootScope.value = 'invalid-email'; }); expect(element).toBeInvalid(); expect(element).toBePristine(); expect(element.hasClass('ng-valid-email')).toBe(false); expect(element.hasClass('ng-invalid-email')).toBe(true); element.val('invalid-again'); browserTrigger(element, ($sniffer.hasEvent('input')) ? 'input' : 'change'); expect(element).toBeInvalid(); expect(element).toBeDirty(); expect(element.hasClass('ng-valid-email')).toBe(false); expect(element.hasClass('ng-invalid-email')).toBe(true); element.val('vojta@google.com'); browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change'); expect(element).toBeValid(); expect(element).toBeDirty(); expect(element.hasClass('ng-valid-email')).toBe(true); expect(element.hasClass('ng-invalid-email')).toBe(false); dealoc(element); })); it('should set invalid classes on init', inject(function($compile, $rootScope) { var element = $compile('')($rootScope); $rootScope.$digest(); expect(element).toBeInvalid(); expect(element).toHaveClass('ng-invalid-required'); })); it('should register/deregister a nested ngModel with parent form when entering or leaving DOM', inject(function($compile, $rootScope) { var element = $compile('
' + '' + '
')($rootScope); var isFormValid; $rootScope.inputPresent = false; $rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; }); $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(true); expect(isFormValid).toBe(true); expect($rootScope.myForm.myControl).toBeUndefined(); $rootScope.inputPresent = true; $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(false); expect(isFormValid).toBe(false); expect($rootScope.myForm.myControl).toBeDefined(); $rootScope.inputPresent = false; $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(true); expect(isFormValid).toBe(true); expect($rootScope.myForm.myControl).toBeUndefined(); dealoc(element); })); it('should register/deregister a nested ngModel with parent form when entering or leaving DOM with animations', function() { // ngAnimate performs the dom manipulation after digest, and since the form validity can be affected by a form // control going away we must ensure that the deregistration happens during the digest while we are still doing // dirty checking. module('ngAnimate'); inject(function($compile, $rootScope) { var element = $compile('
' + '' + '
')($rootScope); var isFormValid; $rootScope.inputPresent = false; // this watch ensure that the form validity gets updated during digest (so that we can observe it) $rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; }); $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(true); expect(isFormValid).toBe(true); expect($rootScope.myForm.myControl).toBeUndefined(); $rootScope.inputPresent = true; $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(false); expect(isFormValid).toBe(false); expect($rootScope.myForm.myControl).toBeDefined(); $rootScope.inputPresent = false; $rootScope.$apply(); expect($rootScope.myForm.$valid).toBe(true); expect(isFormValid).toBe(true); expect($rootScope.myForm.myControl).toBeUndefined(); dealoc(element); }); }); it('should keep previously defined watches consistent when changes in validity are made', inject(function($compile, $rootScope) { var isFormValid; $rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; }); var element = $compile('
' + '' + '
')($rootScope); $rootScope.$apply(); expect(isFormValid).toBe(false); expect($rootScope.myForm.$valid).toBe(false); $rootScope.value='value'; $rootScope.$apply(); expect(isFormValid).toBe(true); expect($rootScope.myForm.$valid).toBe(true); dealoc(element); })); }); describe('input', function() { var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo; function compileInput(inputHtml) { inputElm = jqLite(inputHtml); formElm = jqLite('
'); formElm.append(inputElm); $compile(formElm)(scope); } beforeEach(inject(function($injector, _$sniffer_, _$browser_) { $sniffer = _$sniffer_; $browser = _$browser_; $compile = $injector.get('$compile'); scope = $injector.get('$rootScope'); changeInputValueTo = function(value) { inputElm.val(value); browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change'); }; })); afterEach(function() { dealoc(formElm); }); it('should bind to a model', function() { compileInput(''); scope.$apply(function() { scope.name = 'misko'; }); expect(inputElm.val()).toBe('misko'); }); it('should not set readonly or disabled property on ie7', function() { this.addMatchers({ toBeOff: function(attributeName) { var actualValue = this.actual.attr(attributeName); this.message = function() { return "Attribute '" + attributeName + "' expected to be off but was '" + actualValue + "' in: " + angular.mock.dump(this.actual); } return !actualValue || actualValue == 'false'; } }); compileInput(''); expect(inputElm.prop('readOnly')).toBe(false); expect(inputElm.prop('disabled')).toBe(false); expect(inputElm).toBeOff('readOnly'); expect(inputElm).toBeOff('readonly'); expect(inputElm).toBeOff('disabled'); }); it('should update the model on "blur" event', function() { compileInput(''); changeInputValueTo('adam'); expect(scope.name).toEqual('adam'); }); if (!(msie < 9)) { describe('compositionevents', function() { it('should not update the model between "compositionstart" and "compositionend" on non android', inject(function($sniffer) { $sniffer.android = false; compileInput(''); changeInputValueTo('a'); expect(scope.name).toEqual('a'); browserTrigger(inputElm, 'compositionstart'); changeInputValueTo('adam'); expect(scope.name).toEqual('a'); browserTrigger(inputElm, 'compositionend'); changeInputValueTo('adam'); expect(scope.name).toEqual('adam'); })); it('should update the model between "compositionstart" and "compositionend" on android', inject(function($sniffer) { $sniffer.android = true; compileInput(''); changeInputValueTo('a'); expect(scope.name).toEqual('a'); browserTrigger(inputElm, 'compositionstart'); changeInputValueTo('adam'); expect(scope.name).toEqual('adam'); browserTrigger(inputElm, 'compositionend'); changeInputValueTo('adam2'); expect(scope.name).toEqual('adam2'); })); }); } describe('"change" event', function() { function assertBrowserSupportsChangeEvent(inputEventSupported) { // Force browser to report a lack of an 'input' event $sniffer.hasEvent = function(eventName) { if (eventName === 'input' && !inputEventSupported) { return false; } return true; }; compileInput(''); inputElm.val('mark'); browserTrigger(inputElm, 'change'); expect(scope.name).toEqual('mark'); } it('should update the model event if the browser does not support the "input" event',function() { assertBrowserSupportsChangeEvent(false); }); it('should update the model event if the browser supports the "input" ' + 'event so that form auto complete works',function() { assertBrowserSupportsChangeEvent(true); }); if (!_jqLiteMode) { it('should not cause the double $digest when triggering an event using jQuery', function() { $sniffer.hasEvent = function(eventName) { return eventName !== 'input'; }; compileInput(''); scope.field = 'fake field'; scope.$watch('field', function() { // We need to use _originalTrigger since trigger is modified by Angular Scenario. inputElm._originalTrigger('change'); }); scope.$apply(); }); } }); describe('"paste" and "cut" events', function() { beforeEach(function() { // Force browser to report a lack of an 'input' event $sniffer.hasEvent = function(eventName) { return eventName !== 'input'; }; }); it('should update the model on "paste" event', function() { compileInput(''); inputElm.val('mark'); browserTrigger(inputElm, 'paste'); $browser.defer.flush(); expect(scope.name).toEqual('mark'); }); it('should update the model on "cut" event', function() { compileInput(''); inputElm.val('john'); browserTrigger(inputElm, 'cut'); $browser.defer.flush(); expect(scope.name).toEqual('john'); }); }); it('should update the model and trim the value', function() { compileInput(''); changeInputValueTo(' a '); expect(scope.name).toEqual('a'); }); it('should update the model and not trim the value', function() { compileInput(''); changeInputValueTo(' a '); expect(scope.name).toEqual(' a '); }); it('should allow complex reference binding', function() { compileInput(''); scope.$apply(function() { scope.obj = { abc: { name: 'Misko'} }; }); expect(inputElm.val()).toEqual('Misko'); }); it('should ignore input without ngModel directive', function() { compileInput(''); changeInputValueTo(''); expect(inputElm.hasClass('ng-valid')).toBe(false); expect(inputElm.hasClass('ng-invalid')).toBe(false); expect(inputElm.hasClass('ng-pristine')).toBe(false); expect(inputElm.hasClass('ng-dirty')).toBe(false); }); it('should report error on assignment error', function() { expect(function() { compileInput(''); scope.$digest(); }).toThrowMinErr("$parse", "syntax", "Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at ['']."); }); it("should render as blank if null", function() { compileInput(''); scope.$apply(function() { scope.age = null; }); expect(scope.age).toBeNull(); expect(inputElm.val()).toEqual(''); }); it('should render 0 even if it is a number', function() { compileInput(''); scope.$apply(function() { scope.value = 0; }); expect(inputElm.val()).toBe('0'); }); describe('pattern', function() { it('should validate in-lined pattern', function() { compileInput(''); scope.$digest(); changeInputValueTo('x000-00-0000x'); expect(inputElm).toBeInvalid(); changeInputValueTo('000-00-0000'); expect(inputElm).toBeValid(); changeInputValueTo('000-00-0000x'); expect(inputElm).toBeInvalid(); changeInputValueTo('123-45-6789'); expect(inputElm).toBeValid(); changeInputValueTo('x'); expect(inputElm).toBeInvalid(); }); it('should validate in-lined pattern with modifiers', function() { compileInput(''); scope.$digest(); changeInputValueTo('aB'); expect(inputElm).toBeValid(); changeInputValueTo('xx'); expect(inputElm).toBeInvalid(); }); it('should validate pattern from scope', function() { compileInput(''); scope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/; scope.$digest(); changeInputValueTo('x000-00-0000x'); expect(inputElm).toBeInvalid(); changeInputValueTo('000-00-0000'); expect(inputElm).toBeValid(); changeInputValueTo('000-00-0000x'); expect(inputElm).toBeInvalid(); changeInputValueTo('123-45-6789'); expect(inputElm).toBeValid(); changeInputValueTo('x'); expect(inputElm).toBeInvalid(); scope.regexp = /abc?/; changeInputValueTo('ab'); expect(inputElm).toBeValid(); changeInputValueTo('xx'); expect(inputElm).toBeInvalid(); }); it('should throw an error when scope pattern can\'t be found', function() { expect(function() { compileInput(''); scope.$apply(); }).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/); }); }); describe('minlength', function() { it('should invalid shorter than given minlength', function() { compileInput(''); changeInputValueTo('aa'); expect(scope.value).toBeUndefined(); changeInputValueTo('aaa'); expect(scope.value).toBe('aaa'); }); }); describe('maxlength', function() { it('should invalid shorter than given maxlength', function() { compileInput(''); changeInputValueTo('aaaaaaaa'); expect(scope.value).toBeUndefined(); changeInputValueTo('aaa'); expect(scope.value).toBe('aaa'); }); }); // INPUT TYPES describe('number', function() { it('should reset the model if view is invalid', function() { compileInput(''); scope.$apply(function() { scope.age = 123; }); expect(inputElm.val()).toBe('123'); try { // to allow non-number values, we have to change type so that // the browser which have number validation will not interfere with // this test. IE8 won't allow it hence the catch. inputElm[0].setAttribute('type', 'text'); } catch (e) {} changeInputValueTo('123X'); expect(inputElm.val()).toBe('123X'); expect(scope.age).toBeUndefined(); expect(inputElm).toBeInvalid(); }); it('should render as blank if null', function() { compileInput(''); scope.$apply(function() { scope.age = null; }); expect(scope.age).toBeNull(); expect(inputElm.val()).toEqual(''); }); it('should come up blank when no value specified', function() { compileInput(''); scope.$digest(); expect(inputElm.val()).toBe(''); scope.$apply(function() { scope.age = null; }); expect(scope.age).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { compileInput(''); scope.$apply(function() { scope.age = 10; }); changeInputValueTo(''); expect(scope.age).toBeNull(); expect(inputElm).toBeValid(); }); describe('min', function() { it('should validate', function() { compileInput(''); scope.$digest(); changeInputValueTo('1'); expect(inputElm).toBeInvalid(); expect(scope.value).toBeFalsy(); expect(scope.form.alias.$error.min).toBeTruthy(); changeInputValueTo('100'); expect(inputElm).toBeValid(); expect(scope.value).toBe(100); expect(scope.form.alias.$error.min).toBeFalsy(); }); it('should validate even if min value changes on-the-fly', function(done) { scope.min = 10; compileInput(''); scope.$digest(); changeInputValueTo('5'); expect(inputElm).toBeInvalid(); scope.min = 0; scope.$digest(function () { expect(inputElm).toBeValid(); done(); }); }); }); describe('max', function() { it('should validate', function() { compileInput(''); scope.$digest(); changeInputValueTo('20'); expect(inputElm).toBeInvalid(); expect(scope.value).toBeFalsy(); expect(scope.form.alias.$error.max).toBeTruthy(); changeInputValueTo('0'); expect(inputElm).toBeValid(); expect(scope.value).toBe(0); expect(scope.form.alias.$error.max).toBeFalsy(); }); it('should validate even if max value changes on-the-fly', function(done) { scope.max = 10; compileInput(''); scope.$digest(); changeInputValueTo('5'); expect(inputElm).toBeValid(); scope.max = 0; scope.$digest(function () { expect(inputElm).toBeInvalid(); done(); }); }); }); describe('required', function() { it('should be valid even if value is 0', function() { compileInput(''); changeInputValueTo('0'); expect(inputElm).toBeValid(); expect(scope.value).toBe(0); expect(scope.form.alias.$error.required).toBeFalsy(); }); it('should be valid even if value 0 is set from model', function() { compileInput(''); scope.$apply(function() { scope.value = 0; }); expect(inputElm).toBeValid(); expect(inputElm.val()).toBe('0') expect(scope.form.alias.$error.required).toBeFalsy(); }); it('should register required on non boolean elements', function() { compileInput('
'); scope.$apply(function() { scope.value = ''; }); expect(inputElm).toBeInvalid(); expect(scope.form.alias.$error.required).toBeTruthy(); }); }); }); describe('email', function() { it('should validate e-mail', function() { compileInput(''); var widget = scope.form.alias; changeInputValueTo('vojta@google.com'); expect(scope.email).toBe('vojta@google.com'); expect(inputElm).toBeValid(); expect(widget.$error.email).toBe(false); changeInputValueTo('invalid@'); expect(scope.email).toBeUndefined(); expect(inputElm).toBeInvalid(); expect(widget.$error.email).toBeTruthy(); }); describe('EMAIL_REGEXP', function() { it('should validate email', function() { expect(EMAIL_REGEXP.test('a@b.com')).toBe(true); expect(EMAIL_REGEXP.test('a@b.museum')).toBe(true); expect(EMAIL_REGEXP.test('a@B.c')).toBe(true); expect(EMAIL_REGEXP.test('a@.b.c')).toBe(false); }); }); }); describe('url', function() { it('should validate url', function() { compileInput(''); var widget = scope.form.alias; changeInputValueTo('http://www.something.com'); expect(scope.url).toBe('http://www.something.com'); expect(inputElm).toBeValid(); expect(widget.$error.url).toBe(false); changeInputValueTo('invalid.com'); expect(scope.url).toBeUndefined(); expect(inputElm).toBeInvalid(); expect(widget.$error.url).toBeTruthy(); }); describe('URL_REGEXP', function() { it('should validate url', function() { expect(URL_REGEXP.test('http://server:123/path')).toBe(true); expect(URL_REGEXP.test('a@B.c')).toBe(false); }); }); }); describe('radio', function() { it('should update the model', function() { compileInput( '' + '' + ''); scope.$apply(function() { scope.color = 'white'; }); expect(inputElm[0].checked).toBe(true); expect(inputElm[1].checked).toBe(false); expect(inputElm[2].checked).toBe(false); scope.$apply(function() { scope.color = 'red'; }); expect(inputElm[0].checked).toBe(false); expect(inputElm[1].checked).toBe(true); expect(inputElm[2].checked).toBe(false); browserTrigger(inputElm[2], 'click'); expect(scope.color).toBe('blue'); }); it('should allow {{expr}} as value', function() { scope.some = 11; compileInput( '' + ''); scope.$apply(function() { scope.value = 'blue'; scope.some = 'blue'; scope.other = 'red'; }); expect(inputElm[0].checked).toBe(true); expect(inputElm[1].checked).toBe(false); browserTrigger(inputElm[1], 'click'); expect(scope.value).toBe('red'); scope.$apply(function() { scope.other = 'non-red'; }); expect(inputElm[0].checked).toBe(false); expect(inputElm[1].checked).toBe(false); }); }); describe('checkbox', function() { it('should ignore checkbox without ngModel directive', function() { compileInput(''); changeInputValueTo(''); expect(inputElm.hasClass('ng-valid')).toBe(false); expect(inputElm.hasClass('ng-invalid')).toBe(false); expect(inputElm.hasClass('ng-pristine')).toBe(false); expect(inputElm.hasClass('ng-dirty')).toBe(false); }); it('should format booleans', function() { compileInput(''); scope.$apply(function() { scope.name = false; }); expect(inputElm[0].checked).toBe(false); scope.$apply(function() { scope.name = true; }); expect(inputElm[0].checked).toBe(true); }); it('should support type="checkbox" with non-standard capitalization', function() { compileInput(''); browserTrigger(inputElm, 'click'); expect(scope.checkbox).toBe(true); browserTrigger(inputElm, 'click'); expect(scope.checkbox).toBe(false); }); it('should allow custom enumeration', function() { compileInput(''); scope.$apply(function() { scope.name = 'y'; }); expect(inputElm[0].checked).toBe(true); scope.$apply(function() { scope.name = 'n'; }); expect(inputElm[0].checked).toBe(false); scope.$apply(function() { scope.name = 'something else'; }); expect(inputElm[0].checked).toBe(false); browserTrigger(inputElm, 'click'); expect(scope.name).toEqual('y'); browserTrigger(inputElm, 'click'); expect(scope.name).toEqual('n'); }); it('should be required if false', function() { compileInput(''); browserTrigger(inputElm, 'click'); expect(inputElm[0].checked).toBe(true); expect(inputElm).toBeValid(); browserTrigger(inputElm, 'click'); expect(inputElm[0].checked).toBe(false); expect(inputElm).toBeInvalid(); }); }); describe('textarea', function() { it("should process textarea", function() { compileInput(''); inputElm = formElm.find('textarea'); scope.$apply(function() { scope.name = 'Adam'; }); expect(inputElm.val()).toEqual('Adam'); changeInputValueTo('Shyam'); expect(scope.name).toEqual('Shyam'); changeInputValueTo('Kai'); expect(scope.name).toEqual('Kai'); }); it('should ignore textarea without ngModel directive', function() { compileInput(''); inputElm = formElm.find('textarea'); changeInputValueTo(''); expect(inputElm.hasClass('ng-valid')).toBe(false); expect(inputElm.hasClass('ng-invalid')).toBe(false); expect(inputElm.hasClass('ng-pristine')).toBe(false); expect(inputElm.hasClass('ng-dirty')).toBe(false); }); }); describe('ngList', function() { it('should parse text into an array', function() { compileInput(''); // model -> view scope.$apply(function() { scope.list = ['x', 'y', 'z']; }); expect(inputElm.val()).toBe('x, y, z'); // view -> model changeInputValueTo('1, 2, 3'); expect(scope.list).toEqual(['1', '2', '3']); }); it("should not clobber text if model changes due to itself", function() { // When the user types 'a,b' the 'a,' stage parses to ['a'] but if the // $parseModel function runs it will change to 'a', in essence preventing // the user from ever typying ','. compileInput(''); changeInputValueTo('a '); expect(inputElm.val()).toEqual('a '); expect(scope.list).toEqual(['a']); changeInputValueTo('a ,'); expect(inputElm.val()).toEqual('a ,'); expect(scope.list).toEqual(['a']); changeInputValueTo('a , '); expect(inputElm.val()).toEqual('a , '); expect(scope.list).toEqual(['a']); changeInputValueTo('a , b'); expect(inputElm.val()).toEqual('a , b'); expect(scope.list).toEqual(['a', 'b']); }); it('should convert empty string to an empty array', function() { compileInput(''); changeInputValueTo(''); expect(scope.list).toEqual([]); }); it('should be invalid if required and empty', function() { compileInput(''); changeInputValueTo(''); expect(scope.list).toBeUndefined(); expect(inputElm).toBeInvalid(); changeInputValueTo('a,b'); expect(scope.list).toEqual(['a','b']); expect(inputElm).toBeValid(); }); it('should allow custom separator', function() { compileInput(''); changeInputValueTo('a,a'); expect(scope.list).toEqual(['a,a']); changeInputValueTo('a:b'); expect(scope.list).toEqual(['a', 'b']); }); it('should allow regexp as a separator', function() { compileInput(''); changeInputValueTo('a,b'); expect(scope.list).toEqual(['a', 'b']); changeInputValueTo('a,b: c'); expect(scope.list).toEqual(['a', 'b', 'c']); }); }); describe('required', function() { it('should allow bindings via ngRequired', function() { compileInput(''); scope.$apply(function() { scope.required = false; }); changeInputValueTo(''); expect(inputElm).toBeValid(); scope.$apply(function() { scope.required = true; }); expect(inputElm).toBeInvalid(); scope.$apply(function() { scope.value = 'some'; }); expect(inputElm).toBeValid(); changeInputValueTo(''); expect(inputElm).toBeInvalid(); scope.$apply(function() { scope.required = false; }); expect(inputElm).toBeValid(); }); it('should invalid initial value with bound required', function() { compileInput(''); scope.$apply(function() { scope.required = true; }); expect(inputElm).toBeInvalid(); }); it('should be $invalid but $pristine if not touched', function() { compileInput(''); scope.$apply(function() { scope.name = ''; }); expect(inputElm).toBeInvalid(); expect(inputElm).toBePristine(); changeInputValueTo(''); expect(inputElm).toBeInvalid(); expect(inputElm).toBeDirty(); }); it('should allow empty string if not required', function() { compileInput(''); changeInputValueTo('a'); changeInputValueTo(''); expect(scope.foo).toBe(''); }); it('should set $invalid when model undefined', function() { compileInput(''); scope.$digest(); expect(inputElm).toBeInvalid(); }); it('should allow `false` as a valid value when the input type is not "checkbox"', function() { compileInput('' + ''); scope.$apply(); expect(inputElm).toBeInvalid(); scope.$apply(function() { scope.answer = true; }); expect(inputElm).toBeValid(); scope.$apply(function() { scope.answer = false; }); expect(inputElm).toBeValid(); }); }); describe('ngChange', function() { it('should $eval expression after new value is set in the model', function() { compileInput(''); scope.change = jasmine.createSpy('change').andCallFake(function() { expect(scope.value).toBe('new value'); }); changeInputValueTo('new value'); expect(scope.change).toHaveBeenCalledOnce(); }); it('should not $eval the expression if changed from model', function() { compileInput(''); scope.change = jasmine.createSpy('change'); scope.$apply(function() { scope.value = true; }); expect(scope.change).not.toHaveBeenCalled(); }); it('should $eval ngChange expression on checkbox', function() { compileInput(''); scope.changeFn = jasmine.createSpy('changeFn'); scope.$digest(); expect(scope.changeFn).not.toHaveBeenCalled(); browserTrigger(inputElm, 'click'); expect(scope.changeFn).toHaveBeenCalledOnce(); }); }); describe('ngValue', function() { it('should update the dom "value" property and attribute', function() { compileInput(''); scope.$apply(function() { scope.value = 'something'; }); expect(inputElm[0].value).toBe('something'); expect(inputElm[0].getAttribute('value')).toBe('something'); }); it('should evaluate and set constant expressions', function() { compileInput('' + '' + ''); scope.$digest(); browserTrigger(inputElm[0], 'click'); expect(scope.selected).toBe(true); browserTrigger(inputElm[1], 'click'); expect(scope.selected).toBe(false); browserTrigger(inputElm[2], 'click'); expect(scope.selected).toBe(1); }); it('should watch the expression', function() { compileInput(''); scope.$apply(function() { scope.selected = scope.value = {some: 'object'}; }); expect(inputElm[0].checked).toBe(true); scope.$apply(function() { scope.value = {some: 'other'}; }); expect(inputElm[0].checked).toBe(false); browserTrigger(inputElm, 'click'); expect(scope.selected).toBe(scope.value); }); it('should work inside ngRepeat', function() { compileInput( ''); scope.$apply(function() { scope.items = [{id: 1}, {id: 2}]; scope.selected = 1; }); inputElm = formElm.find('input'); expect(inputElm[0].checked).toBe(true); expect(inputElm[1].checked).toBe(false); browserTrigger(inputElm.eq(1), 'click'); expect(scope.selected).toBe(2); }); it('should work inside ngRepeat with primitive values', function() { compileInput( '
' + '' + '' + '
'); scope.$apply(function() { scope.items = [{id: 1, selected: true}, {id: 2, selected: false}]; }); inputElm = formElm.find('input'); expect(inputElm[0].checked).toBe(true); expect(inputElm[1].checked).toBe(false); expect(inputElm[2].checked).toBe(false); expect(inputElm[3].checked).toBe(true); browserTrigger(inputElm.eq(1), 'click'); expect(scope.items[0].selected).toBe(false); }); it('should work inside ngRepeat without name attribute', function() { compileInput( '
' + '' + '' + '
'); scope.$apply(function() { scope.items = [{id: 1, selected: true}, {id: 2, selected: false}]; }); inputElm = formElm.find('input'); expect(inputElm[0].checked).toBe(true); expect(inputElm[1].checked).toBe(false); expect(inputElm[2].checked).toBe(false); expect(inputElm[3].checked).toBe(true); browserTrigger(inputElm.eq(1), 'click'); expect(scope.items[0].selected).toBe(false); }); }); }); angular.js-1.2.11/test/ng/directive/ngBindSpec.js000066400000000000000000000114621227375216300215670ustar00rootroot00000000000000'use strict'; describe('ngBind*', function() { var element; afterEach(function() { dealoc(element); }); describe('ngBind', function() { it('should set text', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); expect(element.text()).toEqual(''); $rootScope.a = 'misko'; $rootScope.$digest(); expect(element.hasClass('ng-binding')).toEqual(true); expect(element.text()).toEqual('misko'); })); it('should set text to blank if undefined', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.a = 'misko'; $rootScope.$digest(); expect(element.text()).toEqual('misko'); $rootScope.a = undefined; $rootScope.$digest(); expect(element.text()).toEqual(''); $rootScope.a = null; $rootScope.$digest(); expect(element.text()).toEqual(''); })); it('should suppress rendering of falsy values', inject(function($rootScope, $compile) { element = $compile('
' + '' + '-' + '' + '' + '
')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('-0false'); })); }); describe('ngBindTemplate', function() { it('should ngBindTemplate', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.name = 'Misko'; $rootScope.$digest(); expect(element.hasClass('ng-binding')).toEqual(true); expect(element.text()).toEqual('Hello Misko!'); })); it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { element = $compile('
{{ {key:"value", $$key:"hide"}  }}
')($rootScope); $rootScope.$digest(); expect(fromJson(element.text())).toEqual({key:'value'}); })); }); describe('ngBindHtml', function() { describe('SCE disabled', function() { beforeEach(function() { module(function($sceProvider) { $sceProvider.enabled(false); }); }); it('should set html', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.html = '
hello
'; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('
hello
'); })); }); describe('SCE enabled', function() { it('should NOT set html for untrusted values', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.html = '
hello
'; expect($rootScope.$digest).toThrow(); })); it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) { element = $compile('
')($rootScope); $rootScope.html = $sce.trustAsCss('
hello
'); expect($rootScope.$digest).toThrow(); })); it('should set html for trusted values', inject(function($rootScope, $compile, $sce) { element = $compile('
')($rootScope); $rootScope.html = $sce.trustAsHtml('
hello
'); $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('
hello
'); })); it('should watch the string value to avoid infinite recursion', inject(function($rootScope, $compile, $sce) { // Ref: https://github.com/angular/angular.js/issues/3932 // If the binding is a function that creates a new value on every call via trustAs, we'll // trigger an infinite digest if we don't take care of it. element = $compile('
')($rootScope); $rootScope.getHtml = function() { return $sce.trustAsHtml('
hello
'); }; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('
hello
'); })); describe('when $sanitize is available', function() { beforeEach(function() { module('ngSanitize'); }); it('should sanitize untrusted html', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.html = '
hello
'; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('
hello
'); })); }); }); }); }); angular.js-1.2.11/test/ng/directive/ngClassSpec.js000066400000000000000000000367651227375216300217750ustar00rootroot00000000000000'use strict'; describe('ngClass', function() { var element; afterEach(function() { dealoc(element); }); it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynClass = 'A'; $rootScope.$digest(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(true); $rootScope.dynClass = 'B'; $rootScope.$digest(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(false); expect(element.hasClass('B')).toBe(true); delete $rootScope.dynClass; $rootScope.$digest(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(false); expect(element.hasClass('B')).toBe(false); })); it('should support adding multiple classes via an array', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.hasClass('existing')).toBeTruthy(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeTruthy(); })); it('should support adding multiple classes conditionally via a map of class names to boolean' + 'expressions', inject(function($rootScope, $compile) { var element = $compile( '
' + '
')($rootScope); $rootScope.conditionA = true; $rootScope.$digest(); expect(element.hasClass('existing')).toBeTruthy(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeFalsy(); expect(element.hasClass('AnotB')).toBeTruthy(); $rootScope.conditionB = function() { return true; }; $rootScope.$digest(); expect(element.hasClass('existing')).toBeTruthy(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeTruthy(); expect(element.hasClass('AnotB')).toBeFalsy(); })); it('should remove classes when the referenced object is the same but its property is changed', inject(function($rootScope, $compile) { var element = $compile('
')($rootScope); $rootScope.classes = { A: true, B: true }; $rootScope.$digest(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeTruthy(); $rootScope.classes.A = false; $rootScope.$digest(); expect(element.hasClass('A')).toBeFalsy(); expect(element.hasClass('B')).toBeTruthy(); })); it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.hasClass('existing')).toBeTruthy(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeTruthy(); })); it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynClass = 'A'; $rootScope.$digest(); expect(element.hasClass('existing')).toBe(true); // add extra class, change model and eval element.addClass('newClass'); $rootScope.dynClass = 'B'; $rootScope.$digest(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('B')).toBe(true); expect(element.hasClass('newClass')).toBe(true); })); it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynClass = 'A'; $rootScope.$digest(); expect(element.hasClass('A')).toBe(true); // add extra class, change model and eval element.addClass('newClass'); $rootScope.dynClass = 'B'; $rootScope.$digest(); expect(element.hasClass('B')).toBe(true); expect(element.hasClass('newClass')).toBe(true); })); it('should preserve other classes with similar name"', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynCls = 'panel'; $rootScope.$digest(); $rootScope.dynCls = 'foo'; $rootScope.$digest(); expect(element[0].className).toBe('ui-panel ui-selected ng-scope foo'); })); it('should not add duplicate classes', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynCls = 'panel'; $rootScope.$digest(); expect(element[0].className).toBe('panel bar ng-scope'); })); it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynCls = 'panel'; $rootScope.$digest(); $rootScope.dynCls = 'window'; $rootScope.$digest(); expect(element[0].className).toBe('bar ng-scope window'); })); it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynCls = 'foo'; $rootScope.$digest(); element.addClass('foo'); $rootScope.dynCls = ''; $rootScope.$digest(); })); it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.dynCls = [undefined, null]; $rootScope.$digest(); })); it('should ngClass odd/even', inject(function($rootScope, $compile) { element = $compile('
    • ')($rootScope); $rootScope.$digest(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[3]); expect(e1.hasClass('existing')).toBeTruthy(); expect(e1.hasClass('odd')).toBeTruthy(); expect(e2.hasClass('existing')).toBeTruthy(); expect(e2.hasClass('even')).toBeTruthy(); })); it('should allow both ngClass and ngClassOdd/Even on the same element', inject(function($rootScope, $compile) { element = $compile('
        ' + '
      • ' + '
          ')($rootScope); $rootScope.$apply(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[3]); expect(e1.hasClass('plainClass')).toBeTruthy(); expect(e1.hasClass('odd')).toBeTruthy(); expect(e1.hasClass('even')).toBeFalsy(); expect(e2.hasClass('plainClass')).toBeTruthy(); expect(e2.hasClass('even')).toBeTruthy(); expect(e2.hasClass('odd')).toBeFalsy(); })); it('should allow both ngClass and ngClassOdd/Even with multiple classes', inject(function($rootScope, $compile) { element = $compile('
            ' + '
          • ' + '
              ')($rootScope); $rootScope.$apply(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[3]); expect(e1.hasClass('A')).toBeTruthy(); expect(e1.hasClass('B')).toBeTruthy(); expect(e1.hasClass('C')).toBeTruthy(); expect(e1.hasClass('D')).toBeTruthy(); expect(e1.hasClass('E')).toBeFalsy(); expect(e1.hasClass('F')).toBeFalsy(); expect(e2.hasClass('A')).toBeTruthy(); expect(e2.hasClass('B')).toBeTruthy(); expect(e2.hasClass('E')).toBeTruthy(); expect(e2.hasClass('F')).toBeTruthy(); expect(e2.hasClass('C')).toBeFalsy(); expect(e2.hasClass('D')).toBeFalsy(); })); it('should reapply ngClass when interpolated class attribute changes', inject(function($rootScope, $compile) { element = $compile('
              ')($rootScope); $rootScope.$apply(function() { $rootScope.cls = "two"; $rootScope.four = true; }); expect(element).toHaveClass('one'); expect(element).toHaveClass('two'); // interpolated expect(element).toHaveClass('three'); expect(element).toHaveClass('four'); $rootScope.$apply(function() { $rootScope.cls = "too"; }); expect(element).toHaveClass('one'); expect(element).toHaveClass('too'); // interpolated expect(element).toHaveClass('three'); expect(element).toHaveClass('four'); // should still be there expect(element.hasClass('two')).toBeFalsy(); $rootScope.$apply(function() { $rootScope.cls = "to"; }); expect(element).toHaveClass('one'); expect(element).toHaveClass('to'); // interpolated expect(element).toHaveClass('three'); expect(element).toHaveClass('four'); // should still be there expect(element.hasClass('two')).toBeFalsy(); expect(element.hasClass('too')).toBeFalsy(); })); it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) { $rootScope.foo = true; $rootScope.$watch("anything", function() { $rootScope.foo = false; }); element = $compile('
              ')($rootScope); $rootScope.$digest(); expect(element.hasClass('foo')).toBe(false); })); it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) { element = $compile('
                ' + '
              • ' + '
                  ')($rootScope); $rootScope.items = ['a','b','a']; $rootScope.$digest(); $rootScope.items = ['a','a']; $rootScope.$digest(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[3]); expect(e1.hasClass('odd')).toBeTruthy(); expect(e1.hasClass('even')).toBeFalsy(); expect(e2.hasClass('even')).toBeTruthy(); expect(e2.hasClass('odd')).toBeFalsy(); })); it('should update ngClassOdd/Even when model is changed by sorting', inject(function($rootScope, $compile) { element = $compile('
                    ' + '
                  • i
                  • ' + '
                      ')($rootScope); $rootScope.items = ['a','b']; $rootScope.$digest(); $rootScope.items = ['b','a']; $rootScope.$digest(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[3]); expect(e1.hasClass('odd')).toBeTruthy(); expect(e1.hasClass('even')).toBeFalsy(); expect(e2.hasClass('even')).toBeTruthy(); expect(e2.hasClass('odd')).toBeFalsy(); })); }); describe('ngClass animations', function() { var body, element, $rootElement; it("should avoid calling addClass accidentally when removeClass is going on", function() { module('mock.animate'); inject(function($compile, $rootScope, $animate, $timeout) { var element = angular.element('
                      '); var body = jqLite(document.body); body.append(element); $compile(element)($rootScope); expect($animate.queue.length).toBe(0); $rootScope.val = 'one'; $rootScope.$digest(); $animate.flushNext('addClass'); expect($animate.queue.length).toBe(0); $rootScope.val = ''; $rootScope.$digest(); $animate.flushNext('removeClass'); //only removeClass is called expect($animate.queue.length).toBe(0); $rootScope.val = 'one'; $rootScope.$digest(); $animate.flushNext('addClass'); expect($animate.queue.length).toBe(0); $rootScope.val = 'two'; $rootScope.$digest(); $animate.flushNext('removeClass'); $animate.flushNext('addClass'); expect($animate.queue.length).toBe(0); }); }); it("should consider the ngClass expression evaluation before performing an animation", function() { //mocks are not used since the enter delegation method is called before addClass and //it makes it impossible to test to see that addClass is called first module('ngAnimate'); var digestQueue = []; module(function($animateProvider) { $animateProvider.register('.crazy', function() { return { enter : function(element, done) { element.data('state', 'crazy-enter'); done(); } }; }); return function($rootScope) { var before = $rootScope.$$postDigest; $rootScope.$$postDigest = function() { var args = arguments; digestQueue.push(function() { before.apply($rootScope, args); }); }; }; }); inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) { // Enable animations by triggering the first item in the postDigest queue digestQueue.shift()(); // wait for the 2nd animation bootstrap digest to pass $rootScope.$digest(); digestQueue.shift()(); $rootScope.val = 'crazy'; var element = angular.element('
                      '); jqLite($document[0].body).append($rootElement); $compile(element)($rootScope); var enterComplete = false; $animate.enter(element, $rootElement, null, function() { enterComplete = true; }); //jquery doesn't compare both elements properly so let's use the nodes expect(element.parent()[0]).toEqual($rootElement[0]); expect(element.hasClass('crazy')).toBe(false); expect(enterComplete).toBe(false); expect(digestQueue.length).toBe(1); $rootScope.$digest(); $timeout.flush(); expect(element.hasClass('crazy')).toBe(true); expect(enterComplete).toBe(false); digestQueue.shift()(); //enter expect(digestQueue.length).toBe(0); //we don't normally need this, but since the timing between digests //is spaced-out then it is required so that the original digestion //is kicked into gear $rootScope.$digest(); $timeout.flush(); expect(element.data('state')).toBe('crazy-enter'); expect(enterComplete).toBe(true); }); }); it("should not remove classes if they're going to be added back right after", function() { module('mock.animate'); inject(function($rootScope, $compile, $animate) { var className; $rootScope.one = true; $rootScope.two = true; $rootScope.three = true; var element = angular.element('
                      '); $compile(element)($rootScope); $rootScope.$digest(); //this fires twice due to the class observer firing className = $animate.flushNext('addClass').params[1]; expect(className).toBe('one two three'); expect($animate.queue.length).toBe(0); $rootScope.three = false; $rootScope.$digest(); className = $animate.flushNext('removeClass').params[1]; expect(className).toBe('three'); expect($animate.queue.length).toBe(0); $rootScope.two = false; $rootScope.three = true; $rootScope.$digest(); className = $animate.flushNext('removeClass').params[1]; expect(className).toBe('two'); className = $animate.flushNext('addClass').params[1]; expect(className).toBe('three'); expect($animate.queue.length).toBe(0); }); }); }); angular.js-1.2.11/test/ng/directive/ngClickSpec.js000066400000000000000000000013001227375216300217260ustar00rootroot00000000000000'use strict'; describe('ngClick', function() { var element; afterEach(function() { dealoc(element); }); it('should get called on a click', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.clicked).toBeFalsy(); browserTrigger(element, 'click'); expect($rootScope.clicked).toEqual(true); })); it('should pass event object', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); browserTrigger(element, 'click'); expect($rootScope.event).toBeDefined(); })); }); angular.js-1.2.11/test/ng/directive/ngCloakSpec.js000066400000000000000000000026121227375216300217410ustar00rootroot00000000000000'use strict'; describe('ngCloak', function() { var element; afterEach(function() { dealoc(element); }); it('should get removed when an element is compiled', inject(function($rootScope, $compile) { element = jqLite('
                      '); expect(element.attr('ng-cloak')).toBe(''); $compile(element); expect(element.attr('ng-cloak')).toBeUndefined(); })); it('should remove ngCloak class from a compiled element with attribute', inject( function($rootScope, $compile) { element = jqLite('
                      '); expect(element.hasClass('foo')).toBe(true); expect(element.hasClass('ng-cloak')).toBe(true); expect(element.hasClass('bar')).toBe(true); $compile(element); expect(element.hasClass('foo')).toBe(true); expect(element.hasClass('ng-cloak')).toBe(false); expect(element.hasClass('bar')).toBe(true); })); it('should remove ngCloak class from a compiled element', inject(function($rootScope, $compile) { element = jqLite('
                      '); expect(element.hasClass('foo')).toBe(true); expect(element.hasClass('ng-cloak')).toBe(true); expect(element.hasClass('bar')).toBe(true); $compile(element); expect(element.hasClass('foo')).toBe(true); expect(element.hasClass('ng-cloak')).toBe(false); expect(element.hasClass('bar')).toBe(true); })); }); angular.js-1.2.11/test/ng/directive/ngControllerSpec.js000066400000000000000000000104771227375216300230430ustar00rootroot00000000000000'use strict'; describe('ngController', function() { var element; beforeEach(module(function($controllerProvider) { $controllerProvider.register('PublicModule', function() { this.mark = 'works'; }); })); beforeEach(inject(function($window) { $window.Greeter = function($scope) { // private stuff (not exported to scope) this.prefix = 'Hello '; // public stuff (exported to scope) var ctrl = this; $scope.name = 'Misko'; $scope.greet = function(name) { return ctrl.prefix + name + ctrl.suffix; }; $scope.protoGreet = bind(this, this.protoGreet); }; $window.Greeter.prototype = { suffix: '!', protoGreet: function(name) { return this.prefix + name + this.suffix; } }; $window.Child = function($scope) { $scope.name = 'Adam'; }; $window.Public = function() { this.mark = 'works'; } })); afterEach(function() { dealoc(element); }); it('should instantiate controller and bind methods', inject(function($compile, $rootScope) { element = $compile('
                      {{greet(name)}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello Misko!'); })); it('should publish controller into scope', inject(function($compile, $rootScope) { element = $compile('
                      {{p.mark}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('works'); })); it('should publish controller into scope from module', inject(function($compile, $rootScope) { element = $compile('
                      {{p.mark}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('works'); })); it('should allow nested controllers', inject(function($compile, $rootScope) { element = $compile('
                      {{greet(name)}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello Adam!'); dealoc(element); element = $compile('
                      {{protoGreet(name)}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello Adam!'); })); it('should instantiate controller defined on scope', inject(function($compile, $rootScope) { $rootScope.Greeter = function($scope) { $scope.name = 'Vojta'; }; element = $compile('
                      {{name}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Vojta'); })); it('should work with ngInclude on the same element', inject(function($compile, $rootScope, $httpBackend) { $rootScope.GreeterController = function($scope) { $scope.name = 'Vojta'; }; element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'url').respond('{{name}}'); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('Vojta'); })); it('should only instantiate the controller once with ngInclude on the same element', inject(function($compile, $rootScope, $httpBackend) { var count = 0; $rootScope.CountController = function($scope) { count += 1; }; element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'first').respond('first'); $rootScope.url = 'first'; $rootScope.$digest(); $httpBackend.flush(); $httpBackend.expect('GET', 'second').respond('second'); $rootScope.url = 'second'; $rootScope.$digest(); $httpBackend.flush(); expect(count).toBe(1); })); it('when ngInclude is on the same element, the content included content should get a child scope of the controller', inject(function($compile, $rootScope, $httpBackend) { var controllerScope; $rootScope.ExposeScopeController = function($scope) { controllerScope = $scope; }; element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'url').respond('
                      '); $rootScope.$digest(); $httpBackend.flush(); expect(controllerScope.name).toBeUndefined(); })); }); angular.js-1.2.11/test/ng/directive/ngEventDirsSpec.js000066400000000000000000000021721227375216300226140ustar00rootroot00000000000000'use strict'; describe('event directives', function() { var element; afterEach(function() { dealoc(element); }); describe('ngSubmit', function() { it('should get called on form submit', inject(function($rootScope, $compile) { element = $compile('
                      ' + '' + '
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.submitted).not.toBeDefined(); browserTrigger(element.children()[0]); expect($rootScope.submitted).toEqual(true); })); it('should expose event on form submit', inject(function($rootScope, $compile) { $rootScope.formSubmission = function(e) { if (e) { $rootScope.formSubmitted = 'foo'; } }; element = $compile('
                      ' + '' + '
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.formSubmitted).not.toBeDefined(); browserTrigger(element.children()[0]); expect($rootScope.formSubmitted).toEqual('foo'); })); }); }); angular.js-1.2.11/test/ng/directive/ngIfSpec.js000077500000000000000000000177711227375216300212650ustar00rootroot00000000000000'use strict'; describe('ngIf', function () { var $scope, $compile, element, $compileProvider; beforeEach(module(function(_$compileProvider_) { $compileProvider = _$compileProvider_; })); beforeEach(inject(function ($rootScope, _$compile_) { $scope = $rootScope.$new(); $compile = _$compile_; element = $compile('
                      ')($scope); })); afterEach(function () { dealoc(element); }); function makeIf(expr) { element.append($compile('
                      Hi
                      ')($scope)); $scope.$apply(); } it('should immediately remove element if condition is false', function () { makeIf('false'); expect(element.children().length).toBe(0); }); it('should leave the element if condition is true', function () { makeIf('true'); expect(element.children().length).toBe(1); }); it('should not add the element twice if the condition goes from true to true', function () { $scope.hello = 'true1'; makeIf('hello'); expect(element.children().length).toBe(1); $scope.$apply('hello = "true2"'); expect(element.children().length).toBe(1); }); it('should not recreate the element if the condition goes from true to true', function () { $scope.hello = 'true1'; makeIf('hello'); element.children().data('flag', true); $scope.$apply('hello = "true2"'); expect(element.children().data('flag')).toBe(true); }); it('should create then remove the element if condition changes', function () { $scope.hello = true; makeIf('hello'); expect(element.children().length).toBe(1); $scope.$apply('hello = false'); expect(element.children().length).toBe(0); }); it('should create a new scope every time the expression evaluates to true', function () { $scope.$apply('value = true'); element.append($compile( '
                      ' )($scope)); $scope.$apply(); expect(element.children('div').length).toBe(1); }); it('should destroy the child scope every time the expression evaluates to false', function() { $scope.value = true; element.append($compile( '
                      ' )($scope)); $scope.$apply(); var childScope = element.children().scope(); var destroyed = false; childScope.$on('$destroy', function() { destroyed = true; }); $scope.value = false; $scope.$apply(); expect(destroyed).toBe(true); }); it('should play nice with other elements beside it', function () { $scope.values = [1, 2, 3, 4]; element.append($compile( '
                      ' + '
                      ' + '
                      ' )($scope)); $scope.$apply(); expect(element.children().length).toBe(9); $scope.$apply('values.splice(0,1)'); expect(element.children().length).toBe(6); $scope.$apply('values.push(1)'); expect(element.children().length).toBe(9); }); it('should play nice with ngInclude on the same element', inject(function($templateCache) { $templateCache.put('test.html', [200, '{{value}}', {}]); $scope.value = 'first'; element.append($compile( '
                      ' )($scope)); $scope.$apply(); expect(element.text()).toBe('first'); $scope.value = 'later'; $scope.$apply(); expect(element.text()).toBe(''); })); it('should work with multiple elements', function() { $scope.show = true; $scope.things = [1, 2, 3]; element.append($compile( '
                      before;
                      ' + '
                      start;
                      ' + '
                      {{thing}};
                      ' + '
                      end;
                      ' + '
                      after;
                      ' )($scope)); $scope.$apply(); expect(element.text()).toBe('before;start;1;2;3;end;after;'); $scope.things.push(4); $scope.$apply(); expect(element.text()).toBe('before;start;1;2;3;4;end;after;'); $scope.show = false; $scope.$apply(); expect(element.text()).toBe('before;after;'); }); it('should restore the element to its compiled state', function() { $scope.value = true; makeIf('value'); expect(element.children().length).toBe(1); jqLite(element.children()[0]).removeClass('my-class'); expect(element.children()[0].className).not.toContain('my-class'); $scope.$apply('value = false'); expect(element.children().length).toBe(0); $scope.$apply('value = true'); expect(element.children().length).toBe(1); expect(element.children()[0].className).toContain('my-class'); }); it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) { $compileProvider.directive('test', function() { return { templateUrl: 'test.html' }; }); $httpBackend.whenGET('test.html').respond('hello'); element.append('
                      '); $compile(element)($rootScope); $rootScope.show = true; $rootScope.$apply(); expect(element.text()).toBe(''); $httpBackend.flush(); expect(element.text()).toBe('hello'); $rootScope.show = false; $rootScope.$apply(); // Note: there are still comments in element! expect(element.children().length).toBe(0); expect(element.text()).toBe(''); })); }); describe('ngIf and transcludes', function() { it('should allow access to directive controller from children when used in a replace template', function() { var controller; module(function($compileProvider) { var directive = $compileProvider.directive; directive('template', valueFn({ template: '
                      ', replace: true, controller: function() { this.flag = true; } })); directive('test', valueFn({ require: '^template', link: function(scope, el, attr, ctrl) { controller = ctrl; } })); }); inject(function($compile, $rootScope) { var element = $compile('
                      ')($rootScope); $rootScope.$apply(); expect(controller.flag).toBe(true); dealoc(element); }); }); }); describe('ngIf animations', function () { var body, element, $rootElement; function html(html) { $rootElement.html(html); element = $rootElement.children().eq(0); return element; } beforeEach(module('mock.animate')); beforeEach(module(function() { // we need to run animation on attached elements; return function(_$rootElement_) { $rootElement = _$rootElement_; body = jqLite(document.body); body.append($rootElement); }; })); afterEach(function(){ dealoc(body); dealoc(element); }); beforeEach(module(function($animateProvider, $provide) { return function($animate) { $animate.enabled(true); }; })); it('should fire off the enter animation', inject(function($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); element = $compile(html( '
                      ' + '
                      Hi
                      ' + '
                      ' ))($scope); $rootScope.$digest(); $scope.$apply('value = true'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('Hi'); expect(element.children().length).toBe(1); })); it('should fire off the leave animation', inject(function ($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); element = $compile(html( '
                      ' + '
                      Hi
                      ' + '
                      ' ))($scope); $scope.$apply('value = true'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('Hi'); $scope.$apply('value = false'); expect(element.children().length).toBe(1); item = $animate.flushNext('leave').element; expect(item.text()).toBe('Hi'); expect(element.children().length).toBe(0); })); }); angular.js-1.2.11/test/ng/directive/ngIncludeSpec.js000066400000000000000000000506741227375216300223060ustar00rootroot00000000000000'use strict'; describe('ngInclude', function() { var element; afterEach(function(){ dealoc(element); }); function putIntoCache(url, content) { return function($templateCache) { $templateCache.put(url, [200, content, {}]); }; } it('should trust and use literal urls', inject(function( $rootScope, $httpBackend, $compile) { element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'url').respond('template text'); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('template text'); dealoc($rootScope); })); it('should trust and use trusted urls', inject(function($rootScope, $httpBackend, $compile, $sce) { element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'http://foo.bar/url').respond('template text'); $rootScope.fooUrl = $sce.trustAsResourceUrl('http://foo.bar/url'); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('template text'); dealoc($rootScope); })); it('should include an external file', inject(putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { element = jqLite('
                      '); var body = jqLite(document.body); body.append(element); element = $compile(element)($rootScope); $rootScope.name = 'misko'; $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(body.text()).toEqual('misko'); body.empty(); })); it('should support ng-include="src" syntax', inject(putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { element = jqLite('
                      '); jqLite(document.body).append(element); element = $compile(element)($rootScope); $rootScope.name = 'Alibaba'; $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('Alibaba'); jqLite(document.body).empty(); })); it('should NOT use untrusted URL expressions ', inject(putIntoCache('myUrl', '{{name}} text'), function($rootScope, $compile, $sce) { element = jqLite(''); jqLite(document.body).append(element); element = $compile(element)($rootScope); $rootScope.name = 'chirayu'; $rootScope.url = 'http://example.com/myUrl'; expect(function() { $rootScope.$digest(); }).toThrowMinErr( '$sce', 'insecurl', /Blocked loading resource from url not allowed by \$sceDelegate policy. URL: http:\/\/example.com\/myUrl.*/); jqLite(document.body).empty(); })); it('should NOT use mistyped expressions ', inject(putIntoCache('myUrl', '{{name}} text'), function($rootScope, $compile, $sce) { element = jqLite(''); jqLite(document.body).append(element); element = $compile(element)($rootScope); $rootScope.name = 'chirayu'; $rootScope.url = $sce.trustAsUrl('http://example.com/myUrl'); expect(function() { $rootScope.$digest(); }).toThrowMinErr( '$sce', 'insecurl', /Blocked loading resource from url not allowed by \$sceDelegate policy. URL: http:\/\/example.com\/myUrl.*/); jqLite(document.body).empty(); })); it('should remove previously included text if a falsy value is bound to src', inject( putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); $rootScope.name = 'igor'; $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('igor'); $rootScope.url = undefined; $rootScope.$digest(); expect(element.text()).toEqual(''); })); it('should fire $includeContentRequested event on scope after making the xhr call', inject( function ($rootScope, $compile, $httpBackend) { var contentRequestedSpy = jasmine.createSpy('content requested').andCallFake(function (event) { expect(event.targetScope).toBe($rootScope); }); $httpBackend.whenGET('url').respond('my partial'); $rootScope.$on('$includeContentRequested', contentRequestedSpy); element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect(contentRequestedSpy).toHaveBeenCalledOnce(); $httpBackend.flush(); })); it('should fire $includeContentLoaded event on child scope after linking the content', inject( function($rootScope, $compile, $templateCache) { var contentLoadedSpy = jasmine.createSpy('content loaded').andCallFake(function(event) { expect(event.targetScope.$parent).toBe($rootScope); expect(element.text()).toBe('partial content'); }); $templateCache.put('url', [200, 'partial content', {}]); $rootScope.$on('$includeContentLoaded', contentLoadedSpy); element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect(contentLoadedSpy).toHaveBeenCalledOnce(); })); it('should evaluate onload expression when a partial is loaded', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); expect($rootScope.loaded).not.toBeDefined(); $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('my partial'); expect($rootScope.loaded).toBe(true); })); it('should create child scope and destroy old one', inject( function($rootScope, $compile, $httpBackend) { $httpBackend.whenGET('url1').respond('partial {{$parent.url}}'); $httpBackend.whenGET('url2').respond(404); element = $compile('
                      ')($rootScope); expect(element.children().scope()).toBeFalsy(); $rootScope.url = 'url1'; $rootScope.$digest(); $httpBackend.flush(); expect(element.children().scope().$parent).toBe($rootScope); expect(element.text()).toBe('partial url1'); $rootScope.url = 'url2'; $rootScope.$digest(); $httpBackend.flush(); expect($rootScope.$$childHead).toBeFalsy(); expect(element.text()).toBe(''); $rootScope.url = 'url1'; $rootScope.$digest(); expect(element.children().scope().$parent).toBe($rootScope); $rootScope.url = null; $rootScope.$digest(); expect($rootScope.$$childHead).toBeFalsy(); })); it('should do xhr request and cache it', inject(function($rootScope, $httpBackend, $compile) { element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'myUrl').respond('my partial'); $rootScope.url = 'myUrl'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('my partial'); $rootScope.url = null; $rootScope.$digest(); expect(element.text()).toEqual(''); $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('my partial'); dealoc($rootScope); })); it('should clear content when error during xhr request', inject(function($httpBackend, $compile, $rootScope) { element = $compile('
                      content
                      ')($rootScope); $httpBackend.expect('GET', 'myUrl').respond(404, ''); $rootScope.url = 'myUrl'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe(''); })); it('should be async even if served from cache', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.url = 'myUrl'; var called = 0; // we want to assert only during first watch $rootScope.$watch(function() { if (!called++) expect(element.text()).toBe(''); }); $rootScope.$digest(); expect(element.text()).toBe('my partial'); })); it('should discard pending xhr callbacks if a new template is requested before the current ' + 'finished loading', inject(function($rootScope, $compile, $httpBackend) { element = jqLite("
                      "); var log = {}; $rootScope.templateUrl = 'myUrl1'; $rootScope.logger = function(msg) { log[msg] = true; } $compile(element)($rootScope); expect(log).toEqual({}); $httpBackend.expect('GET', 'myUrl1').respond('
                      {{logger("url1")}}
                      '); $rootScope.$digest(); expect(log).toEqual({}); $rootScope.templateUrl = 'myUrl2'; $httpBackend.expect('GET', 'myUrl2').respond('
                      {{logger("url2")}}
                      '); $httpBackend.flush(); // now that we have two requests pending, flush! expect(log).toEqual({ url2 : true }); })); it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { // regression var onload = jasmine.createSpy('$includeContentLoaded'); $rootScope.$on('$includeContentLoaded', onload); $templateCache.put('tpl.html', [200, 'partial {{tpl}}', {}]); element = $compile('
                      ' + '
                      ')($rootScope); expect(onload).not.toHaveBeenCalled(); $rootScope.$apply(function() { $rootScope.tpl = 'tpl.html'; }); expect(onload).toHaveBeenCalledOnce(); $rootScope.tpl = ''; $rootScope.$digest(); dealoc(element); })); it('should not break attribute bindings on the same element', inject(function($compile, $rootScope, $httpBackend) { // regression #3793 element = $compile('
                      ')($rootScope); $httpBackend.expect('GET', 'url1').respond('template text 1'); $rootScope.hrefUrl = 'fooUrl1'; $rootScope.includeUrl = 'url1'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe('template text 1'); expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); $httpBackend.expect('GET', 'url2').respond('template text 2'); $rootScope.includeUrl = 'url2'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe('template text 2'); expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); $rootScope.hrefUrl = 'fooUrl2'; $rootScope.$digest(); expect(element.text()).toBe('template text 2'); expect(element.find('span').attr('foo')).toBe('#/fooUrl2'); })); it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) { if (!jQuery) { return; } element = $compile('
                      ')($rootScope); // the element needs to be appended for the script to run element.appendTo(document.body); window._ngIncludeCausesScriptToRun = false; $httpBackend.expect('GET', 'url1').respond(''); $rootScope.includeUrl = 'url1'; $rootScope.$digest(); $httpBackend.flush(); expect(window._ngIncludeCausesScriptToRun).toBe(true); // IE8 doesn't like deleting properties of window window._ngIncludeCausesScriptToRun = undefined; try { delete window._ngIncludeCausesScriptToRun; } catch (e) {} })); describe('autoscroll', function() { var autoScrollSpy; function spyOnAnchorScroll() { return function($provide) { autoScrollSpy = jasmine.createSpy('$anchorScroll'); $provide.value('$anchorScroll', autoScrollSpy); }; } function compileAndLink(tpl) { return function($compile, $rootScope) { element = $compile(tpl)($rootScope); }; } beforeEach(module(spyOnAnchorScroll(), 'mock.animate')); beforeEach(inject( putIntoCache('template.html', 'CONTENT'), putIntoCache('another.html', 'CONTENT'))); it('should call $anchorScroll if autoscroll attribute is present', inject( compileAndLink('
                      '), function($rootScope, $animate, $timeout) { $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; }); expect(autoScrollSpy).not.toHaveBeenCalled(); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); it('should call $anchorScroll if autoscroll evaluates to true', inject(function($rootScope, $compile, $animate, $timeout) { element = $compile('
                      ')($rootScope); $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; $rootScope.value = true; }); $animate.flushNext('enter'); $timeout.flush(); $rootScope.$apply(function () { $rootScope.tpl = 'another.html'; $rootScope.value = 'some-string'; }); $animate.flushNext('leave'); $animate.flushNext('enter'); $timeout.flush(); $rootScope.$apply(function() { $rootScope.tpl = 'template.html'; $rootScope.value = 100; }); $animate.flushNext('leave'); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).toHaveBeenCalled(); expect(autoScrollSpy.callCount).toBe(3); })); it('should not call $anchorScroll if autoscroll attribute is not present', inject( compileAndLink('
                      '), function($rootScope, $animate, $timeout) { $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; }); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); it('should not call $anchorScroll if autoscroll evaluates to false', inject(function($rootScope, $compile, $animate, $timeout) { element = $compile('
                      ')($rootScope); $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; $rootScope.value = false; }); $animate.flushNext('enter'); $timeout.flush(); $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; $rootScope.value = undefined; }); $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; $rootScope.value = null; }); expect(autoScrollSpy).not.toHaveBeenCalled(); })); it('should only call $anchorScroll after the "enter" animation completes', inject( compileAndLink('
                      '), function($rootScope, $animate, $timeout) { expect(autoScrollSpy).not.toHaveBeenCalled(); $rootScope.$apply("tpl = 'template.html'"); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); }); }); describe('ngInclude and transcludes', function() { var element, directive; beforeEach(module(function($compileProvider) { element = null; directive = $compileProvider.directive; })); afterEach(function() { if (element) { dealoc(element); } }); it('should allow access to directive controller from children when used in a replace template', function() { var controller; module(function() { directive('template', valueFn({ template: '
                      ', replace: true, controller: function() { this.flag = true; } })); directive('test', valueFn({ require: '^template', link: function(scope, el, attr, ctrl) { controller = ctrl; } })); }); inject(function($compile, $rootScope, $httpBackend) { $httpBackend.expectGET('include.html').respond('
                      '); element = $compile('
                      ')($rootScope); $rootScope.$apply(); $httpBackend.flush(); expect(controller.flag).toBe(true); }); }); it("should compile it's content correctly (although we remove it later)", function() { var testElement; module(function() { directive('test', function() { return { link: function(scope, element) { testElement = element; } }; }); }); inject(function($compile, $rootScope, $httpBackend) { $httpBackend.expectGET('include.html').respond(' '); element = $compile('
                      ')($rootScope); $rootScope.$apply(); $httpBackend.flush(); expect(testElement[0].nodeName).toBe('DIV'); }); }); it('should link directives on the same element after the content has been loaded', function() { var contentOnLink; module(function() { directive('test', function() { return { link: function(scope, element) { contentOnLink = element.text(); } }; }); }); inject(function($compile, $rootScope, $httpBackend) { $httpBackend.expectGET('include.html').respond('someContent'); element = $compile('
                      ')($rootScope); $rootScope.$apply(); $httpBackend.flush(); expect(contentOnLink).toBe('someContent'); }); }); it('should add the content to the element before compiling it', function() { var root; module(function() { directive('test', function() { return { link: function(scope, element) { root = element.parent().parent(); } }; }); }); inject(function($compile, $rootScope, $httpBackend) { $httpBackend.expectGET('include.html').respond(''); element = $compile('
                      ')($rootScope); $rootScope.$apply(); $httpBackend.flush(); expect(root[0]).toBe(element[0]); }); }); }); describe('ngInclude animations', function() { var body, element, $rootElement; function html(html) { $rootElement.html(html); element = $rootElement.children().eq(0); return element; } beforeEach(module(function() { // we need to run animation on attached elements; return function(_$rootElement_) { $rootElement = _$rootElement_; body = jqLite(document.body); body.append($rootElement); }; })); afterEach(function(){ dealoc(body); dealoc(element); }); beforeEach(module('mock.animate')); afterEach(function(){ dealoc(element); }); it('should fire off the enter animation', inject(function($compile, $rootScope, $templateCache, $animate) { var item; $templateCache.put('enter', [200, '
                      data
                      ', {}]); $rootScope.tpl = 'enter'; element = $compile(html( '
                      ' + '
                      ' ))($rootScope); $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('data'); })); it('should fire off the leave animation', inject(function($compile, $rootScope, $templateCache, $animate) { var item; $templateCache.put('enter', [200, '
                      data
                      ', {}]); $rootScope.tpl = 'enter'; element = $compile(html( '
                      ' + '
                      ' ))($rootScope); $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('data'); $rootScope.tpl = ''; $rootScope.$digest(); item = $animate.flushNext('leave').element; expect(item.text()).toBe('data'); })); it('should animate two separate ngInclude elements', inject(function($compile, $rootScope, $templateCache, $animate) { var item; $templateCache.put('one', [200, 'one', {}]); $templateCache.put('two', [200, 'two', {}]); $rootScope.tpl = 'one'; element = $compile(html( '
                      ' + '
                      ' ))($rootScope); $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('one'); $rootScope.tpl = 'two'; $rootScope.$digest(); var itemA = $animate.flushNext('leave').element; var itemB = $animate.flushNext('enter').element; expect(itemA.attr('ng-include')).toBe('tpl'); expect(itemB.attr('ng-include')).toBe('tpl'); expect(itemA).not.toEqual(itemB); })); }); angular.js-1.2.11/test/ng/directive/ngInitSpec.js000066400000000000000000000025651227375216300216220ustar00rootroot00000000000000'use strict'; describe('ngInit', function() { var element; afterEach(function() { dealoc(element); }); it("should init model", inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); expect($rootScope.a).toEqual(123); })); it("should be evaluated before ngInclude", inject(function($rootScope, $templateCache, $compile) { $templateCache.put('template1.tpl', '1'); $templateCache.put('template2.tpl', '2'); $rootScope.template = 'template1.tpl'; element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.template).toEqual('template2.tpl'); expect(element.find('span').text()).toEqual('2'); })); it("should be evaluated after ngController", function() { module(function($controllerProvider) { $controllerProvider.register('TestCtrl', function($scope) {}); }); inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.test).toBeUndefined(); expect(element.children('div').scope().test).toEqual(123); }); }); }); angular.js-1.2.11/test/ng/directive/ngKeySpec.js000066400000000000000000000032701227375216300214410ustar00rootroot00000000000000'use strict'; describe('ngKeyup and ngKeydown directives', function() { var element; afterEach(function() { dealoc(element); }); it('should get called on a keyup', inject(function($rootScope, $compile) { element = $compile('')($rootScope); $rootScope.$digest(); expect($rootScope.touched).toBeFalsy(); browserTrigger(element, 'keyup'); expect($rootScope.touched).toEqual(true); })); it('should get called on a keydown', inject(function($rootScope, $compile) { element = $compile('')($rootScope); $rootScope.$digest(); expect($rootScope.touched).toBeFalsy(); browserTrigger(element, 'keydown'); expect($rootScope.touched).toEqual(true); })); it('should get called on a keypress', inject(function($rootScope, $compile) { element = $compile('')($rootScope); $rootScope.$digest(); expect($rootScope.touched).toBeFalsy(); browserTrigger(element, 'keypress'); expect($rootScope.touched).toEqual(true); })); it('should get called on focus', inject(function($rootScope, $compile) { element = $compile('')($rootScope); $rootScope.$digest(); expect($rootScope.touched).toBeFalsy(); browserTrigger(element, 'focus'); expect($rootScope.touched).toEqual(true); })); it('should get called on blur', inject(function($rootScope, $compile) { element = $compile('')($rootScope); $rootScope.$digest(); expect($rootScope.touched).toBeFalsy(); browserTrigger(element, 'blur'); expect($rootScope.touched).toEqual(true); })); }); angular.js-1.2.11/test/ng/directive/ngNonBindableSpec.js000066400000000000000000000026421227375216300230660ustar00rootroot00000000000000'use strict'; describe('ngNonBindable', function() { var element; afterEach(function(){ dealoc(element); }); it('should prevent compilation of the owning element and its children', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); element = $compile('
                      ' + ' {{a}}' + ' ' + '
                      ' + ' {{b}}' + '
                      ' + ' {{a}}' + ' ' + '
                      ')($rootScope); $rootScope.a = "one"; $rootScope.b = "two"; $rootScope.$digest(); // Bindings not contained by ng-non-bindable should resolve. var spans = element.find("span"); expect(spans.eq(0).text()).toEqual('one'); expect(spans.eq(1).text()).toEqual('two'); expect(spans.eq(3).text()).toEqual('one'); expect(spans.eq(4).text()).toEqual('two'); // Bindings contained by ng-non-bindable should be left alone. var nonBindableDiv = element.find("div"); expect(nonBindableDiv.attr('foo')).toEqual('{{a}}'); expect(trim(nonBindableDiv.text())).toEqual('{{b}}'); })); }); angular.js-1.2.11/test/ng/directive/ngPluralizeSpec.js000066400000000000000000000220271227375216300226610ustar00rootroot00000000000000'use strict'; describe('ngPluralize', function() { var element, elementAlt; afterEach(function(){ dealoc(element); dealoc(elementAlt); }); describe('deal with pluralized strings without offset', function() { beforeEach(inject(function($rootScope, $compile) { element = $compile( '" + '')($rootScope); elementAlt = $compile( '" + '')($rootScope); })); it('should show single/plural strings', inject(function($rootScope) { $rootScope.email = 0; $rootScope.$digest(); expect(element.text()).toBe('You have no new email'); expect(elementAlt.text()).toBe('You have no new email'); $rootScope.email = '0'; $rootScope.$digest(); expect(element.text()).toBe('You have no new email'); expect(elementAlt.text()).toBe('You have no new email'); $rootScope.email = 1; $rootScope.$digest(); expect(element.text()).toBe('You have one new email'); expect(elementAlt.text()).toBe('You have one new email'); $rootScope.email = 0.01; $rootScope.$digest(); expect(element.text()).toBe('You have 0.01 new emails'); expect(elementAlt.text()).toBe('You have 0.01 new emails'); $rootScope.email = '0.1'; $rootScope.$digest(); expect(element.text()).toBe('You have 0.1 new emails'); expect(elementAlt.text()).toBe('You have 0.1 new emails'); $rootScope.email = 2; $rootScope.$digest(); expect(element.text()).toBe('You have 2 new emails'); expect(elementAlt.text()).toBe('You have 2 new emails'); $rootScope.email = -0.1; $rootScope.$digest(); expect(element.text()).toBe('You have -0.1 new emails'); expect(elementAlt.text()).toBe('You have -0.1 new emails'); $rootScope.email = '-0.01'; $rootScope.$digest(); expect(element.text()).toBe('You have -0.01 new emails'); expect(elementAlt.text()).toBe('You have -0.01 new emails'); $rootScope.email = -2; $rootScope.$digest(); expect(element.text()).toBe('You have -2 new emails'); expect(elementAlt.text()).toBe('You have -2 new emails'); $rootScope.email = -1; $rootScope.$digest(); expect(element.text()).toBe('You have negative email. Whohoo!'); expect(elementAlt.text()).toBe('You have negative email. Whohoo!'); })); it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) { $rootScope.email = ''; $rootScope.$digest(); expect(element.text()).toBe(''); expect(elementAlt.text()).toBe(''); $rootScope.email = null; $rootScope.$digest(); expect(element.text()).toBe(''); expect(elementAlt.text()).toBe(''); $rootScope.email = undefined; $rootScope.$digest(); expect(element.text()).toBe(''); expect(elementAlt.text()).toBe(''); $rootScope.email = 'a3'; $rootScope.$digest(); expect(element.text()).toBe(''); expect(elementAlt.text()).toBe(''); $rootScope.email = '011'; $rootScope.$digest(); expect(element.text()).toBe('You have 11 new emails'); expect(elementAlt.text()).toBe('You have 11 new emails'); $rootScope.email = '-011'; $rootScope.$digest(); expect(element.text()).toBe('You have -11 new emails'); expect(elementAlt.text()).toBe('You have -11 new emails'); $rootScope.email = '1fff'; $rootScope.$digest(); expect(element.text()).toBe('You have one new email'); expect(elementAlt.text()).toBe('You have one new email'); $rootScope.email = '0aa22'; $rootScope.$digest(); expect(element.text()).toBe('You have no new email'); expect(elementAlt.text()).toBe('You have no new email'); $rootScope.email = '000001'; $rootScope.$digest(); expect(element.text()).toBe('You have one new email'); expect(elementAlt.text()).toBe('You have one new email'); })); }); describe('edge cases', function() { it('should be able to handle empty strings as possible values', inject(function($rootScope, $compile) { element = $compile( '" + '')($rootScope); $rootScope.email = '0'; $rootScope.$digest(); expect(element.text()).toBe(''); })); }); describe('deal with pluralized strings with offset', function() { it('should show single/plural strings with offset', inject(function($rootScope, $compile) { element = $compile( "" + "")($rootScope); elementAlt = $compile( "" + "")($rootScope); $rootScope.p1 = 'Igor'; $rootScope.p2 = 'Misko'; $rootScope.viewCount = 0; $rootScope.$digest(); expect(element.text()).toBe('Nobody is viewing.'); expect(elementAlt.text()).toBe('Nobody is viewing.'); $rootScope.viewCount = 1; $rootScope.$digest(); expect(element.text()).toBe('Igor is viewing.'); expect(elementAlt.text()).toBe('Igor is viewing.'); $rootScope.viewCount = 2; $rootScope.$digest(); expect(element.text()).toBe('Igor and Misko are viewing.'); expect(elementAlt.text()).toBe('Igor and Misko are viewing.'); $rootScope.viewCount = 3; $rootScope.$digest(); expect(element.text()).toBe('Igor, Misko and one other person are viewing.'); expect(elementAlt.text()).toBe('Igor, Misko and one other person are viewing.'); $rootScope.viewCount = 4; $rootScope.$digest(); expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.'); expect(elementAlt.text()).toBe('Igor, Misko and 2 other people are viewing.'); })); }); describe('interpolation', function() { it('should support custom interpolation symbols', function() { module(function($interpolateProvider) { $interpolateProvider.startSymbol('[[').endSymbol('%%'); }); inject(function($compile, $rootScope) { element = $compile( "" + "")($rootScope); elementAlt = $compile( "" + "")($rootScope); $rootScope.p1 = 'Igor'; $rootScope.viewCount = 0; $rootScope.$digest(); expect(element.text()).toBe('Nobody is viewing.'); expect(elementAlt.text()).toBe('Nobody is viewing.'); $rootScope.viewCount = 1; $rootScope.$digest(); expect(element.text()).toBe('Igor is viewing.'); expect(elementAlt.text()).toBe('Igor is viewing.'); $rootScope.viewCount = 2; $rootScope.$digest(); expect(element.text()).toBe('Igor and one other person are viewing.'); expect(elementAlt.text()).toBe('Igor and one other person are viewing.'); $rootScope.viewCount = 3; $rootScope.$digest(); expect(element.text()).toBe('Igor and 2 other people are viewing.'); expect(elementAlt.text()).toBe('Igor and 2 other people are viewing.'); }); }) }); }); angular.js-1.2.11/test/ng/directive/ngRepeatSpec.js000066400000000000000000001163341227375216300221370ustar00rootroot00000000000000'use strict'; describe('ngRepeat', function() { var element, $compile, scope, $exceptionHandler, $compileProvider; beforeEach(module(function(_$compileProvider_) { $compileProvider = _$compileProvider_; })); beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); beforeEach(inject(function(_$compile_, $rootScope, _$exceptionHandler_) { $compile = _$compile_; $exceptionHandler = _$exceptionHandler_; scope = $rootScope.$new(); })); afterEach(function() { if ($exceptionHandler.errors.length) { dump(jasmine.getEnv().currentSpec.getFullName()); dump('$exceptionHandler has errors'); dump($exceptionHandler.errors); expect($exceptionHandler.errors).toBe([]); } dealoc(element); }); it('should iterate over an array of objects', function() { element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); Array.prototype.extraProperty = "should be ignored"; // INIT scope.items = [{name: 'misko'}, {name:'shyam'}]; scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('misko;shyam;'); delete Array.prototype.extraProperty; // GROW scope.items.push({name: 'adam'}); scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('misko;shyam;adam;'); // SHRINK scope.items.pop(); scope.items.shift(); scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('shyam;'); }); it('should iterate over an array-like object', function() { element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); document.body.innerHTML = "

                      " + "a" + "b" + "c" + "

                      "; var htmlCollection = document.getElementsByTagName('a'); scope.items = htmlCollection; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('x;y;x;'); }); it('should iterate over an array-like class', function() { function Collection() {} Collection.prototype = new Array(); Collection.prototype.length = 0; var collection = new Collection(); collection.push({ name: "x" }); collection.push({ name: "y" }); collection.push({ name: "z" }); element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); scope.items = collection; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('x;y;z;'); }); it('should iterate over on object/map', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{value}}|
                      • ' + '
                      ')(scope); scope.items = {misko:'swe', shyam:'set'}; scope.$digest(); expect(element.text()).toEqual('misko:swe|shyam:set|'); }); it('should iterate over an object/map with identical values', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{value}}|
                      • ' + '
                      ')(scope); scope.items = {age:20, wealth:20, prodname: "Bingo", dogname: "Bingo", codename: "20"}; scope.$digest(); expect(element.text()).toEqual('age:20|codename:20|dogname:Bingo|prodname:Bingo|wealth:20|'); }); describe('track by', function() { it('should track using expression function', function() { element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); scope.items = [{id: 'misko'}, {id: 'igor'}]; scope.$digest(); var li0 = element.find('li')[0]; var li1 = element.find('li')[1]; scope.items.push(scope.items.shift()); scope.$digest(); expect(element.find('li')[0]).toBe(li1); expect(element.find('li')[1]).toBe(li0); }); it("should throw an exception if 'track by' evaluates to 'hasOwnProperty'", function() { scope.items = {age:20}; $compile('
                      ')(scope); scope.$digest(); expect($exceptionHandler.errors.shift().message).toMatch(/ng:badname/); }); it('should track using build in $id function', function() { element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); scope.items = [{name: 'misko'}, {name: 'igor'}]; scope.$digest(); var li0 = element.find('li')[0]; var li1 = element.find('li')[1]; scope.items.push(scope.items.shift()); scope.$digest(); expect(element.find('li')[0]).toBe(li1); expect(element.find('li')[1]).toBe(li0); }); it('should still filter when track is present', function() { scope.isIgor = function (item) { return item.name === 'igor'; }; element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); scope.items = [{name: 'igor'}, {name: 'misko'}]; scope.$digest(); expect(element.find('li').text()).toBe('igor;'); }); it('should track using provided function when a filter is present', function() { scope.newArray = function (items) { var newArray = []; angular.forEach(items, function (item) { newArray.push({ id: item.id, name: item.name }); }); return newArray; }; element = $compile( '
                        ' + '
                      • {{item.name}};
                      • ' + '
                      ')(scope); scope.items = [ {id: 1, name: 'igor'}, {id: 2, name: 'misko'} ]; scope.$digest(); expect(element.text()).toBe('igor;misko;'); var li0 = element.find('li')[0]; var li1 = element.find('li')[1]; scope.items.push(scope.items.shift()); scope.$digest(); expect(element.find('li')[0]).toBe(li1); expect(element.find('li')[1]).toBe(li0); }); it('should iterate over an array of primitives', function() { element = $compile( '
                        ' + '
                      • {{item}};
                      • ' + '
                      ')(scope); Array.prototype.extraProperty = "should be ignored"; // INIT scope.items = [true, true, true]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('true;true;true;'); delete Array.prototype.extraProperty; scope.items = [false, true, true]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('false;true;true;'); scope.items = [false, true, false]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('false;true;false;'); scope.items = [true]; scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('true;'); scope.items = [true, true, false]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('true;true;false;'); scope.items = [true, false, false]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('true;false;false;'); // string scope.items = ['a', 'a', 'a']; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('a;a;a;'); scope.items = ['ab', 'a', 'a']; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('ab;a;a;'); scope.items = ['test']; scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('test;'); scope.items = ['same', 'value']; scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('same;value;'); // number scope.items = [12, 12, 12]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('12;12;12;'); scope.items = [53, 12, 27]; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('53;12;27;'); scope.items = [89]; scope.$digest(); expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('89;'); scope.items = [89, 23]; scope.$digest(); expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('89;23;'); }); it('should iterate over object with changing primitive property values', function() { // test for issue #933 element = $compile( '
                        ' + '
                      • ' + '{{key}}:{{value}};' + '' + '
                      • ' + '
                      ')(scope); scope.items = {misko: true, shyam: true, zhenbo:true}; scope.$digest(); expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;'); browserTrigger(element.find('input').eq(0), 'click'); expect(element.text()).toEqual('misko:false;shyam:true;zhenbo:true;'); expect(element.find('input')[0].checked).toBe(false); expect(element.find('input')[1].checked).toBe(true); expect(element.find('input')[2].checked).toBe(true); browserTrigger(element.find('input').eq(0), 'click'); expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;'); expect(element.find('input')[0].checked).toBe(true); expect(element.find('input')[1].checked).toBe(true); expect(element.find('input')[2].checked).toBe(true); browserTrigger(element.find('input').eq(1), 'click'); expect(element.text()).toEqual('misko:true;shyam:false;zhenbo:true;'); expect(element.find('input')[0].checked).toBe(true); expect(element.find('input')[1].checked).toBe(false); expect(element.find('input')[2].checked).toBe(true); scope.items = {misko: false, shyam: true, zhenbo: true}; scope.$digest(); expect(element.text()).toEqual('misko:false;shyam:true;zhenbo:true;'); expect(element.find('input')[0].checked).toBe(false); expect(element.find('input')[1].checked).toBe(true); expect(element.find('input')[2].checked).toBe(true); }); }); it('should allow expressions over multiple lines', function() { element = $compile( '
                        ' + '
                      • {{item.name}}/
                      • ' + '
                      ')(scope); scope.isTrue = function() {return true;}; scope.items = [{name: 'igor'}, {name: 'misko'}]; scope.$digest(); expect(element.text()).toEqual('igor/misko/'); }); it('should strip white space characters correctly', function() { element = $compile( '
                        ' + '
                      • {{item.name}}/
                      • ' + '
                      ')(scope); scope.items = [{name: 'igor'}, {name: 'misko'}]; scope.$digest(); expect(element.text()).toEqual('misko/'); }); it('should not ngRepeat over parent properties', function() { var Class = function() {}; Class.prototype.abc = function() {}; Class.prototype.value = 'abc'; element = $compile( '
                        ' + '
                      • {{key}}:{{value}};
                      • ' + '
                      ')(scope); scope.items = new Class(); scope.items.name = 'value'; scope.$digest(); expect(element.text()).toEqual('name:value;'); }); it('should error on wrong parsing of ngRepeat', function() { element = jqLite('
                      '); $compile(element)(scope); expect($exceptionHandler.errors.shift()[0].message). toMatch(/^\[ngRepeat:iexp\] Expected expression in form of '_item_ in _collection_\[ track by _id_\]' but got 'i dont parse'\./); }); it("should throw error when left-hand-side of ngRepeat can't be parsed", function() { element = jqLite('
                      '); $compile(element)(scope); expect($exceptionHandler.errors.shift()[0].message). toMatch(/^\[ngRepeat:iidexp\] '_item_' in '_item_ in _collection_' should be an identifier or '\(_key_, _value_\)' expression, but got 'i dont parse'\./); }); it('should expose iterator offset as $index when iterating over arrays', function() { element = $compile( '
                        ' + '
                      • {{item}}:{{$index}}|
                      • ' + '
                      ')(scope); scope.items = ['misko', 'shyam', 'frodo']; scope.$digest(); expect(element.text()).toEqual('misko:0|shyam:1|frodo:2|'); }); it('should expose iterator offset as $index when iterating over objects', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}:{{$index}}|
                      • ' + '
                      ')(scope); scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; scope.$digest(); expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|'); }); it('should expose iterator position as $first, $middle and $last when iterating over arrays', function() { element = $compile( '
                        ' + '
                      • {{item}}:{{$first}}-{{$middle}}-{{$last}}|
                      • ' + '
                      ')(scope); scope.items = ['misko', 'shyam', 'doug']; scope.$digest(); expect(element.text()). toEqual('misko:true-false-false|shyam:false-true-false|doug:false-false-true|'); scope.items.push('frodo'); scope.$digest(); expect(element.text()). toEqual('misko:true-false-false|' + 'shyam:false-true-false|' + 'doug:false-true-false|' + 'frodo:false-false-true|'); scope.items.pop(); scope.items.pop(); scope.$digest(); expect(element.text()).toEqual('misko:true-false-false|shyam:false-false-true|'); scope.items.pop(); scope.$digest(); expect(element.text()).toEqual('misko:true-false-true|'); }); it('should expose iterator position as $even and $odd when iterating over arrays', function() { element = $compile( '
                        ' + '
                      • {{item}}:{{$even}}-{{$odd}}|
                      • ' + '
                      ')(scope); scope.items = ['misko', 'shyam', 'doug']; scope.$digest(); expect(element.text()). toEqual('misko:true-false|shyam:false-true|doug:true-false|'); scope.items.push('frodo'); scope.$digest(); expect(element.text()). toBe('misko:true-false|' + 'shyam:false-true|' + 'doug:true-false|' + 'frodo:false-true|'); scope.items.shift(); scope.items.pop(); scope.$digest(); expect(element.text()).toBe('shyam:true-false|doug:false-true|'); }); it('should expose iterator position as $first, $middle and $last when iterating over objects', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}:{{$first}}-{{$middle}}-{{$last}}|
                      • ' + '
                      ')(scope); scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; scope.$digest(); expect(element.text()). toEqual('doug:d:true-false-false|' + 'frodo:f:false-true-false|' + 'misko:m:false-true-false|' + 'shyam:s:false-false-true|'); delete scope.items.doug; delete scope.items.frodo; scope.$digest(); expect(element.text()).toEqual('misko:m:true-false-false|shyam:s:false-false-true|'); delete scope.items.shyam; scope.$digest(); expect(element.text()).toEqual('misko:m:true-false-true|'); }); it('should expose iterator position as $even and $odd when iterating over objects', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}:{{$even}}-{{$odd}}|
                      • ' + '
                      ')(scope); scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; scope.$digest(); expect(element.text()). toBe('doug:d:true-false|' + 'frodo:f:false-true|' + 'misko:m:true-false|' + 'shyam:s:false-true|'); delete scope.items.frodo; delete scope.items.shyam; scope.$digest(); expect(element.text()).toBe('doug:d:true-false|misko:m:false-true|'); }); it('should calculate $first, $middle and $last when we filter out properties from an obj', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}:{{$first}}-{{$middle}}-{{$last}}|
                      • ' + '
                      ')(scope); scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'}; scope.$digest(); expect(element.text()). toEqual('doug:d:true-false-false|' + 'frodo:f:false-true-false|' + 'misko:m:false-true-false|' + 'shyam:s:false-false-true|'); }); it('should calculate $even and $odd when we filter out properties from an obj', function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}:{{$even}}-{{$odd}}|
                      • ' + '
                      ')(scope); scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'}; scope.$digest(); expect(element.text()). toEqual('doug:d:true-false|' + 'frodo:f:false-true|' + 'misko:m:true-false|' + 'shyam:s:false-true|'); }); it('should ignore $ and $$ properties', function() { element = $compile('
                      • {{i}}|
                      ')(scope); scope.items = ['a', 'b', 'c']; scope.items.$$hashKey = 'xxx'; scope.items.$root = 'yyy'; scope.$digest(); expect(element.text()).toEqual('a|b|c|'); }); it('should repeat over nested arrays', function() { element = $compile( '
                        ' + '
                      • ' + '
                        {{group}}|
                        X' + '
                      • ' + '
                      ')(scope); scope.groups = [['a', 'b'], ['c','d']]; scope.$digest(); expect(element.text()).toEqual('a|b|Xc|d|X'); }); it('should ignore non-array element properties when iterating over an array', function() { element = $compile('
                      • {{item}}|
                      ')(scope); scope.array = ['a', 'b', 'c']; scope.array.foo = '23'; scope.array.bar = function() {}; scope.$digest(); expect(element.text()).toBe('a|b|c|'); }); it('should iterate over non-existent elements of a sparse array', function() { element = $compile('
                      • {{item}}|
                      ')(scope); scope.array = ['a', 'b']; scope.array[4] = 'c'; scope.array[6] = 'd'; scope.$digest(); expect(element.text()).toBe('a|b|||c||d|'); }); it('should iterate over all kinds of types', function() { element = $compile('
                      • {{item}}|
                      ')(scope); scope.array = ['a', 1, null, undefined, {}]; scope.$digest(); expect(element.text()).toMatch(/a\|1\|\|\|\{\s*\}\|/); }); it('should preserve data on move of elements', function() { element = $compile('
                      • {{item}}|
                      ')(scope); scope.array = ['a', 'b']; scope.$digest(); var lis = element.find('li'); lis.eq(0).data('mark', 'a'); lis.eq(1).data('mark', 'b'); scope.array = ['b', 'a']; scope.$digest(); lis = element.find('li'); expect(lis.eq(0).data('mark')).toEqual('b'); expect(lis.eq(1).data('mark')).toEqual('a'); }); describe('nesting in replaced directive templates', function() { it('should work when placed on a non-root element of attr directive with SYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('rr', function() { return { restrict: 'A', replace: true, template: '
                      {{i}}|
                      ' }; }); element = jqLite('
                      {{i}}|
                      '); $compile(element)($rootScope); $rootScope.$apply(); expect(element.text()).toBe(''); $rootScope.items = [1, 2]; $rootScope.$apply(); expect(element.text()).toBe('1|2|'); expect(sortedHtml(element)).toBe( '
                      ' + '' + '
                      1|
                      ' + '' + '
                      2|
                      ' + '' + '
                      ' ); })); it('should work when placed on a non-root element of attr directive with ASYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('rr', function() { return { restrict: 'A', replace: true, templateUrl: 'rr.html' }; }); $templateCache.put('rr.html', '
                      {{i}}|
                      '); element = jqLite('
                      {{i}}|
                      '); $compile(element)($rootScope); $rootScope.$apply(); expect(element.text()).toBe(''); $rootScope.items = [1, 2]; $rootScope.$apply(); expect(element.text()).toBe('1|2|'); expect(sortedHtml(element)).toBe( '
                      ' + '' + '
                      1|
                      ' + '' + '
                      2|
                      ' + '' + '
                      ' ); })); it('should work when placed on a root element of attr directive with SYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('replaceMeWithRepeater', function() { return { replace: true, template: '{{log(i)}}' }; }); element = jqLite(''); $compile(element)($rootScope); expect(element.text()).toBe(''); var logs = []; $rootScope.log = function(t) { logs.push(t); }; // This creates one item, but it has no parent so we can't get to it $rootScope.items = [1, 2]; $rootScope.$apply(); // This cleans up to prevent memory leak $rootScope.items = []; $rootScope.$apply(); expect(angular.mock.dump(element)).toBe(''); expect(logs).toEqual([1, 2, 1, 2]); })); it('should work when placed on a root element of attr directive with ASYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('replaceMeWithRepeater', function() { return { replace: true, templateUrl: 'replace-me-with-repeater.html' }; }); $templateCache.put('replace-me-with-repeater.html', '
                      {{log(i)}}
                      '); element = jqLite('--'); $compile(element)($rootScope); expect(element.text()).toBe('--'); var logs = []; $rootScope.log = function(t) { logs.push(t); }; // This creates one item, but it has no parent so we can't get to it $rootScope.items = [1, 2]; $rootScope.$apply(); // This cleans up to prevent memory leak $rootScope.items = []; $rootScope.$apply(); expect(sortedHtml(element)).toBe('--'); expect(logs).toEqual([1, 2, 1, 2]); })); it('should work when placed on a root element of element directive with SYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('replaceMeWithRepeater', function() { return { restrict: 'E', replace: true, template: '
                      {{i}}
                      ' }; }); element = $compile('
                      ')($rootScope); expect(element.text()).toBe(''); $rootScope.$apply(); expect(element.text()).toBe('123'); })); if (!msie || msie > 8) { // only IE>8 supports element directives it('should work when placed on a root element of element directive with ASYNC replaced template', inject(function($templateCache, $compile, $rootScope) { $compileProvider.directive('replaceMeWithRepeater', function() { return { restrict: 'E', replace: true, templateUrl: 'replace-me-with-repeater.html' }; }); $templateCache.put('replace-me-with-repeater.html', '
                      {{i}}
                      '); element = $compile('
                      ')($rootScope); expect(element.text()).toBe(''); $rootScope.$apply(); expect(element.text()).toBe('123'); })); } it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) { $compileProvider.directive('test', function() { return { templateUrl: 'test.html' }; }); $httpBackend.whenGET('test.html').respond('hello'); element = jqLite('
                      '); $compile(element)($rootScope); $rootScope.items = [1]; $rootScope.$apply(); expect(element.text()).toBe(''); $httpBackend.flush(); expect(element.text()).toBe('hello'); $rootScope.items = []; $rootScope.$apply(); // Note: there are still comments in element! expect(element.children().length).toBe(0); expect(element.text()).toBe(''); })); }); it('should add separator comments after each item', inject(function ($compile, $rootScope) { var check = function () { var children = element.find('div'); expect(children.length).toBe(3); // Note: COMMENT_NODE === 8 expect(children[0].nextSibling.nodeType).toBe(8); expect(children[0].nextSibling.nodeValue).toBe(' end ngRepeat: val in values '); expect(children[1].nextSibling.nodeType).toBe(8); expect(children[1].nextSibling.nodeValue).toBe(' end ngRepeat: val in values '); expect(children[2].nextSibling.nodeType).toBe(8); expect(children[2].nextSibling.nodeValue).toBe(' end ngRepeat: val in values '); } $rootScope.values = [1, 2, 3]; element = $compile( '
                      ' + '
                      val:{{val}};
                      ' + '
                      ' )($rootScope); $rootScope.$digest(); check(); $rootScope.values.shift(); $rootScope.values.push(4); $rootScope.$digest(); check(); })); it('should remove whole block even if the number of elements inside it changes', inject( function ($compile, $rootScope) { $rootScope.values = [1, 2, 3]; element = $compile( '
                      ' + '
                      ' + '{{val}}' + '

                      ' + '
                      ' )($rootScope); $rootScope.$digest(); var ends = element.find('p'); expect(ends.length).toBe(3); // insert an extra element inside the second block var extra = angular.element('')[0]; element[0].insertBefore(extra, ends[1]); $rootScope.values.splice(1, 1); $rootScope.$digest(); // expect the strong tag to be removed too expect(childrenTagsOf(element)).toEqual([ 'div', 'span', 'p', 'div', 'span', 'p' ]); })); it('should move whole block even if the number of elements inside it changes', inject( function ($compile, $rootScope) { $rootScope.values = [1, 2, 3]; element = $compile( '
                      ' + '
                      ' + '{{val}}' + '

                      ' + '
                      ' )($rootScope); $rootScope.$digest(); var ends = element.find('p'); expect(ends.length).toBe(3); // insert an extra element inside the third block var extra = angular.element('')[0]; element[0].insertBefore(extra, ends[2]); // move the third block to the begining $rootScope.values.unshift($rootScope.values.pop()); $rootScope.$digest(); // expect the strong tag to be moved too expect(childrenTagsOf(element)).toEqual([ 'div', 'span', 'strong', 'p', 'div', 'span', 'p', 'div', 'span', 'p' ]); })); describe('stability', function() { var a, b, c, d, lis; beforeEach(function() { element = $compile( '
                        ' + '
                      • {{key}}:{{val}}|>
                      • ' + '
                      ')(scope); a = {}; b = {}; c = {}; d = {}; scope.items = [a, b, c]; scope.$digest(); lis = element.find('li'); }); it('should preserve the order of elements', function() { scope.items = [a, c, d]; scope.$digest(); var newElements = element.find('li'); expect(newElements[0]).toEqual(lis[0]); expect(newElements[1]).toEqual(lis[2]); expect(newElements[2]).not.toEqual(lis[1]); }); it('should throw error on adding existing duplicates and recover', function() { scope.items = [a, a, a]; scope.$digest(); expect($exceptionHandler.errors.shift().message). toMatch(/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:003/); // recover scope.items = [a]; scope.$digest(); var newElements = element.find('li'); expect(newElements.length).toEqual(1); expect(newElements[0]).toEqual(lis[0]); scope.items = []; scope.$digest(); newElements = element.find('li'); expect(newElements.length).toEqual(0); }); it('should throw error on new duplicates and recover', function() { scope.items = [d, d, d]; scope.$digest(); expect($exceptionHandler.errors.shift().message). toMatch(/^\[ngRepeat:dupes\] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:009/); // recover scope.items = [a]; scope.$digest(); var newElements = element.find('li'); expect(newElements.length).toEqual(1); expect(newElements[0]).toEqual(lis[0]); scope.items = []; scope.$digest(); newElements = element.find('li'); expect(newElements.length).toEqual(0); }); it('should reverse items when the collection is reversed', function() { scope.items = [a, b, c]; scope.$digest(); lis = element.find('li'); scope.items = [c, b, a]; scope.$digest(); var newElements = element.find('li'); expect(newElements.length).toEqual(3); expect(newElements[0]).toEqual(lis[2]); expect(newElements[1]).toEqual(lis[1]); expect(newElements[2]).toEqual(lis[0]); }); it('should reuse elements even when model is composed of primitives', function() { // rebuilding repeater from scratch can be expensive, we should try to avoid it even for // model that is composed of primitives. scope.items = ['hello', 'cau', 'ahoj']; scope.$digest(); lis = element.find('li'); lis[2].id = 'yes'; scope.items = ['ahoj', 'hello', 'cau']; scope.$digest(); var newLis = element.find('li'); expect(newLis.length).toEqual(3); expect(newLis[0]).toEqual(lis[2]); expect(newLis[1]).toEqual(lis[0]); expect(newLis[2]).toEqual(lis[1]); }); it('should be stable even if the collection is initially undefined', function () { scope.items = undefined; scope.$digest(); scope.items = [ { name: 'A' }, { name: 'B' }, { name: 'C' } ]; scope.$digest(); lis = element.find('li'); scope.items.shift(); scope.$digest(); var newLis = element.find('li'); expect(newLis.length).toBe(2); expect(newLis[0]).toBe(lis[1]); }); }); describe('compatibility', function() { it('should allow mixing ngRepeat and another element transclusion directive', function() { $compileProvider.directive('elmTrans', valueFn({ transclude: 'element', controller: function($transclude, $scope, $element) { $transclude(function(transcludedNodes) { $element.after(']]').after(transcludedNodes).after('[['); }); } })); inject(function($compile, $rootScope) { element = $compile('
                      {{i}}
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('[[1]][[2]]') }); }); it('should allow mixing ngRepeat with ngInclude', inject(function($compile, $rootScope, $httpBackend) { $httpBackend.whenGET('someTemplate.html').respond('

                      some template;

                      '); element = $compile('
                      ')($rootScope); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe('some template; some template; '); })); it('should allow mixing ngRepeat with ngIf', inject(function($compile, $rootScope) { element = $compile('
                      {{i}};
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('2;4;'); })); }); describe('ngRepeatStart', function () { it('should grow multi-node repeater', inject(function($compile, $rootScope) { $rootScope.show = false; $rootScope.books = [ {title:'T1', description: 'D1'}, {title:'T2', description: 'D2'} ]; element = $compile( '
                      ' + '
                      {{book.title}}:
                      ' + '
                      {{book.description}};
                      ' + '
                      ')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('T1:D1;T2:D2;'); $rootScope.books.push({title:'T3', description: 'D3'}); $rootScope.$digest(); expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;'); })); it('should not clobber ng-if when updating collection', inject(function ($compile, $rootScope) { $rootScope.values = [1, 2, 3]; $rootScope.showMe = true; element = $compile( '
                      ' + '
                      val:{{val}};
                      ' + '
                      if:{{val}};
                      ' + '
                      ' )($rootScope); $rootScope.$digest(); expect(element.find('div').length).toBe(6); $rootScope.values.shift(); $rootScope.values.push(4); $rootScope.$digest(); expect(element.find('div').length).toBe(6); expect(element.text()).not.toContain('if:1;'); })); }); }); describe('ngRepeat and transcludes', function() { it('should allow access to directive controller from children when used in a replace template', function() { var controller; module(function($compileProvider) { var directive = $compileProvider.directive; directive('template', valueFn({ template: '
                      ', replace: true, controller: function() { this.flag = true; } })); directive('test', valueFn({ require: '^template', link: function(scope, el, attr, ctrl) { controller = ctrl; } })); }); inject(function($compile, $rootScope) { var element = $compile('
                      ')($rootScope); $rootScope.$apply(); expect(controller.flag).toBe(true); dealoc(element); }); }); }); describe('ngRepeat animations', function() { var body, element, $rootElement; function html(html) { $rootElement.html(html); element = $rootElement.children().eq(0); return element; } beforeEach(module('mock.animate')); beforeEach(module(function() { // we need to run animation on attached elements; return function(_$rootElement_) { $rootElement = _$rootElement_; body = jqLite(document.body); body.append($rootElement); }; })); afterEach(function(){ dealoc(body); dealoc(element); }); it('should fire off the enter animation', inject(function($compile, $rootScope, $animate) { var item; element = $compile(html( '
                      ' + '{{ item }}' + '
                      ' ))($rootScope); $rootScope.$digest(); // re-enable the animations; $rootScope.items = ['1','2','3']; $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('1'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('2'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('3'); })); it('should fire off the leave animation', inject(function($compile, $rootScope, $animate) { var item; element = $compile(html( '
                      ' + '{{ item }}' + '
                      ' ))($rootScope); $rootScope.items = ['1','2','3']; $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('1'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('2'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('3'); $rootScope.items = ['1','3']; $rootScope.$digest(); item = $animate.flushNext('leave').element; expect(item.text()).toBe('2'); })); it('should fire off the move animation', inject(function($compile, $rootScope, $animate) { var item; element = $compile(html( '
                      ' + '
                      ' + '{{ item }}' + '
                      ' + '
                      ' ))($rootScope); $rootScope.items = ['1','2','3']; $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('1'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('2'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('3'); $rootScope.items = ['2','3','1']; $rootScope.$digest(); item = $animate.flushNext('move').element; expect(item.text()).toBe('2'); item = $animate.flushNext('move').element; expect(item.text()).toBe('1'); })); }); angular.js-1.2.11/test/ng/directive/ngShowHideSpec.js000066400000000000000000000070241227375216300224240ustar00rootroot00000000000000'use strict'; describe('ngShow / ngHide', function() { var element; afterEach(function() { dealoc(element); }); describe('ngShow', function() { it('should show and hide an element', inject(function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); $rootScope.$digest(); expect(element).toBeHidden(); $rootScope.exp = true; $rootScope.$digest(); expect(element).toBeShown(); })); // https://github.com/angular/angular.js/issues/5414 it('should show if the expression is a function with a no arguments', inject(function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); $rootScope.exp = function(){}; $rootScope.$digest(); expect(element).toBeShown(); })); it('should make hidden element visible', inject(function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); expect(element).toBeHidden(); $rootScope.exp = true; $rootScope.$digest(); expect(element).toBeShown(); })); }); describe('ngHide', function() { it('should hide an element', inject(function($rootScope, $compile) { element = jqLite('
                      '); element = $compile(element)($rootScope); expect(element).toBeShown(); $rootScope.exp = true; $rootScope.$digest(); expect(element).toBeHidden(); })); }); }); describe('ngShow / ngHide animations', function() { var body, element, $rootElement; function html(html) { body.append($rootElement); $rootElement.html(html); element = $rootElement.children().eq(0); return element; } beforeEach(function() { // we need to run animation on attached elements; body = jqLite(document.body); }); afterEach(function(){ dealoc(body); dealoc(element); body.removeAttr('ng-animation-running'); }); beforeEach(module('mock.animate')); beforeEach(module(function($animateProvider, $provide) { return function(_$rootElement_) { $rootElement = _$rootElement_; }; })); describe('ngShow', function() { it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); $scope.on = true; element = $compile(html( '
                      data
                      ' ))($scope); $scope.$digest(); item = $animate.flushNext('removeClass').element; expect(item.text()).toBe('data'); expect(item).toBeShown(); $scope.on = false; $scope.$digest(); item = $animate.flushNext('addClass').element; expect(item.text()).toBe('data'); expect(item).toBeHidden(); })); }); describe('ngHide', function() { it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); $scope.off = true; element = $compile(html( '
                      datum
                      ' ))($scope); $scope.$digest(); item = $animate.flushNext('addClass').element; expect(item.text()).toBe('datum'); expect(item).toBeHidden(); $scope.off = false; $scope.$digest(); item = $animate.flushNext('removeClass').element; expect(item.text()).toBe('datum'); expect(item).toBeShown(); })); }); }); angular.js-1.2.11/test/ng/directive/ngSrcSpec.js000066400000000000000000000054041227375216300214410ustar00rootroot00000000000000'use strict'; describe('ngSrc', function() { var element; afterEach(function() { dealoc(element); }); it('should not result empty string in img src', inject(function($rootScope, $compile) { $rootScope.image = {}; element = $compile('')($rootScope); $rootScope.$digest(); expect(element.attr('src')).not.toBe(''); expect(element.attr('src')).toBe(undefined); })); describe('iframe[ng-src]', function() { it('should pass through src attributes for the same domain', inject(function($compile, $rootScope) { element = $compile('')($rootScope); $rootScope.testUrl = "different_page"; $rootScope.$apply(); expect(element.attr('src')).toEqual('different_page'); })); it('should error on src attributes for a different domain', inject(function($compile, $rootScope) { element = $compile('')($rootScope); $rootScope.testUrl = "http://a.different.domain.example.com"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "http://a.different.domain.example.com"); })); it('should error on JS src attributes', inject(function($compile, $rootScope) { element = $compile('')($rootScope); $rootScope.testUrl = "javascript:alert(1);"; expect(function() { $rootScope.$apply() }).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "javascript:alert(1);"); })); it('should error on non-resource_url src attributes', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()"); expect($rootScope.$apply).toThrowMinErr( "$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " + "loading resource from url not allowed by $sceDelegate policy. URL: " + "javascript:doTrustedStuff()"); })); it('should pass through $sce.trustAs() values in src attributes', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()"); $rootScope.$apply(); expect(element.attr('src')).toEqual('javascript:doTrustedStuff()'); })); }); }); angular.js-1.2.11/test/ng/directive/ngSrcsetSpec.js000066400000000000000000000006201227375216300221500ustar00rootroot00000000000000'use strict'; describe('ngSrcset', function() { var element; afterEach(function() { dealoc(element); }); it('should not result empty string in img srcset', inject(function($rootScope, $compile) { $rootScope.image = {}; element = $compile('')($rootScope); $rootScope.$digest(); expect(element.attr('srcset')).toEqual(' 2x'); })); }); angular.js-1.2.11/test/ng/directive/ngStyleSpec.js000066400000000000000000000054551227375216300220200ustar00rootroot00000000000000'use strict'; describe('ngStyle', function() { var element; afterEach(function() { dealoc(element); }); it('should set', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect(element.css('height')).toEqual('40px'); })); it('should silently ignore undefined style', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect(element.hasClass('ng-exception')).toBeFalsy(); })); describe('preserving styles set before and after compilation', function() { var scope, preCompStyle, preCompVal, postCompStyle, postCompVal, element; beforeEach(inject(function($rootScope, $compile) { preCompStyle = 'width'; preCompVal = '300px'; postCompStyle = 'height'; postCompVal = '100px'; element = jqLite('
                      '); element.css(preCompStyle, preCompVal); jqLite(document.body).append(element); $compile(element)($rootScope); scope = $rootScope; scope.styleObj = {'margin-top': '44px'}; scope.$apply(); element.css(postCompStyle, postCompVal); })); afterEach(function() { element.remove(); }); it('should not mess up stuff after compilation', function() { element.css('margin', '44px'); expect(element.css(preCompStyle)).toBe(preCompVal); expect(element.css('margin-top')).toBe('44px'); expect(element.css(postCompStyle)).toBe(postCompVal); }); it('should not mess up stuff after $apply with no model changes', function() { element.css('padding-top', '33px'); scope.$apply(); expect(element.css(preCompStyle)).toBe(preCompVal); expect(element.css('margin-top')).toBe('44px'); expect(element.css(postCompStyle)).toBe(postCompVal); expect(element.css('padding-top')).toBe('33px'); }); it('should not mess up stuff after $apply with non-colliding model changes', function() { scope.styleObj = {'padding-top': '99px'}; scope.$apply(); expect(element.css(preCompStyle)).toBe(preCompVal); expect(element.css('margin-top')).not.toBe('44px'); expect(element.css('padding-top')).toBe('99px'); expect(element.css(postCompStyle)).toBe(postCompVal); }); it('should overwrite original styles after a colliding model change', function() { scope.styleObj = {'height': '99px', 'width': '88px'}; scope.$apply(); expect(element.css(preCompStyle)).toBe('88px'); expect(element.css(postCompStyle)).toBe('99px'); scope.styleObj = {}; scope.$apply(); expect(element.css(preCompStyle)).not.toBe('88px'); expect(element.css(postCompStyle)).not.toBe('99px'); }); }); }); angular.js-1.2.11/test/ng/directive/ngSwitchSpec.js000066400000000000000000000221131227375216300221470ustar00rootroot00000000000000'use strict'; describe('ngSwitch', function() { var element; afterEach(function(){ dealoc(element); }); it('should switch on value change', inject(function($rootScope, $compile) { element = $compile( '
                      ' + '
                      first:{{name}}
                      ' + '
                      second:{{name}}
                      ' + '
                      true:{{name}}
                      ' + '
                      ')($rootScope); expect(element.html()).toEqual( ''); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('first:'); $rootScope.name="shyam"; $rootScope.$apply(); expect(element.text()).toEqual('first:shyam'); $rootScope.select = 2; $rootScope.$apply(); expect(element.text()).toEqual('second:shyam'); $rootScope.name = 'misko'; $rootScope.$apply(); expect(element.text()).toEqual('second:misko'); $rootScope.select = true; $rootScope.$apply(); expect(element.text()).toEqual('true:misko'); })); it('should show all switch-whens that match the current value', inject(function($rootScope, $compile) { element = $compile( '
                        ' + '
                      • first:{{name}}
                      • ' + '
                      • , first too:{{name}}
                      • ' + '
                      • second:{{name}}
                      • ' + '
                      • true:{{name}}
                      • ' + '
                      ')($rootScope); expect(element.html()).toEqual('' + '' + '' + ''); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('first:, first too:'); $rootScope.name="shyam"; $rootScope.$apply(); expect(element.text()).toEqual('first:shyam, first too:shyam'); $rootScope.select = 2; $rootScope.$apply(); expect(element.text()).toEqual('second:shyam'); $rootScope.name = 'misko'; $rootScope.$apply(); expect(element.text()).toEqual('second:misko'); $rootScope.select = true; $rootScope.$apply(); expect(element.text()).toEqual('true:misko'); })); it('should switch on switch-when-default', inject(function($rootScope, $compile) { element = $compile( '' + '
                      one
                      ' + '
                      other
                      ' + '
                      ')($rootScope); $rootScope.$apply(); expect(element.text()).toEqual('other'); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('one'); })); it('should show all switch-when-default', inject(function($rootScope, $compile) { element = $compile( '
                        ' + '
                      • one
                      • ' + '
                      • other
                      • ' + '
                      • , other too
                      • ' + '
                      ')($rootScope); $rootScope.$apply(); expect(element.text()).toEqual('other, other too'); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('one'); })); it('should always display the elements that do not match a switch', inject(function($rootScope, $compile) { element = $compile( '
                        ' + '
                      • always
                      • ' + '
                      • one
                      • ' + '
                      • two
                      • ' + '
                      • other,
                      • ' + '
                      • other too
                      • ' + '
                      ')($rootScope); $rootScope.$apply(); expect(element.text()).toEqual('always other, other too '); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('always one '); })); it('should display the elements that do not have ngSwitchWhen nor ' + 'ngSwitchDefault at the position specified in the template, when the ' + 'first and last elements in the ngSwitch body do not have a ngSwitch* ' + 'directive', inject(function($rootScope, $compile) { element = $compile( '
                        ' + '
                      • 1
                      • ' + '
                      • 2
                      • ' + '
                      • 3
                      • ' + '
                      • 4
                      • ' + '
                      • 5
                      • ' + '
                      • 6
                      • ' + '
                      • 7
                      • ' + '
                      • 8
                      • ' + '
                      ')($rootScope); $rootScope.$apply(); expect(element.text()).toEqual('135678'); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('12368'); })); it('should display the elements that do not have ngSwitchWhen nor ' + 'ngSwitchDefault at the position specified in the template when the ' + 'first and last elements in the ngSwitch have a ngSwitch* directive', inject(function($rootScope, $compile) { element = $compile( '
                        ' + '
                      • 2
                      • ' + '
                      • 3
                      • ' + '
                      • 4
                      • ' + '
                      • 5
                      • ' + '
                      • 6
                      • ' + '
                      • 7
                      • ' + '
                      ')($rootScope); $rootScope.$apply(); expect(element.text()).toEqual('3567'); $rootScope.select = 1; $rootScope.$apply(); expect(element.text()).toEqual('236'); })); it('should call change on switch', inject(function($rootScope, $compile) { element = $compile( '' + '
                      {{name}}
                      ' + '
                      ')($rootScope); $rootScope.url = 'a'; $rootScope.$apply(); expect($rootScope.name).toEqual('works'); expect(element.text()).toEqual('works'); })); it('should properly create and destroy child scopes', inject(function($rootScope, $compile) { element = $compile( '' + '
                      {{name}}
                      ' + '
                      ')($rootScope); $rootScope.$apply(); var getChildScope = function() { return element.find('div').scope(); }; expect(getChildScope()).toBeUndefined(); $rootScope.url = 'a'; $rootScope.$apply(); var child1 = getChildScope(); expect(child1).toBeDefined(); spyOn(child1, '$destroy'); $rootScope.url = 'x'; $rootScope.$apply(); expect(getChildScope()).toBeUndefined(); expect(child1.$destroy).toHaveBeenCalledOnce(); $rootScope.url = 'a'; $rootScope.$apply(); var child2 = getChildScope(); expect(child2).toBeDefined(); expect(child2).not.toBe(child1); })); it('should not leak jq data when compiled but not attached to parent when parent is destroyed', inject(function($rootScope, $compile) { element = $compile( '
                      ' + '' + '
                      {{name}}
                      ' + '
                      ' + '
                      ')($rootScope); $rootScope.$apply(); // element now contains only empty repeater. this element is dealocated by local afterEach. // afterwards a global afterEach will check for leaks in jq data cache object })); }); describe('ngSwitch animations', function() { var body, element, $rootElement; function html(html) { $rootElement.html(html); element = $rootElement.children().eq(0); return element; } beforeEach(module('mock.animate')); beforeEach(module(function() { // we need to run animation on attached elements; return function(_$rootElement_) { $rootElement = _$rootElement_; body = jqLite(document.body); body.append($rootElement); }; })); afterEach(function(){ dealoc(body); dealoc(element); }); it('should fire off the enter animation', inject(function($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); element = $compile(html( '
                      ' + '
                      one
                      ' + '
                      two
                      ' + '
                      three
                      ' + '
                      ' ))($scope); $rootScope.$digest(); // re-enable the animations; $scope.val = 'one'; $scope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('one'); })); it('should fire off the leave animation', inject(function($compile, $rootScope, $animate) { var item; var $scope = $rootScope.$new(); element = $compile(html( '
                      ' + '
                      one
                      ' + '
                      two
                      ' + '
                      three
                      ' + '
                      ' ))($scope); $rootScope.$digest(); // re-enable the animations; $scope.val = 'two'; $scope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('two'); $scope.val = 'three'; $scope.$digest(); item = $animate.flushNext('leave').element; expect(item.text()).toBe('two'); item = $animate.flushNext('enter').element; expect(item.text()).toBe('three'); })); }); angular.js-1.2.11/test/ng/directive/scriptSpec.js000066400000000000000000000023351227375216300216710ustar00rootroot00000000000000'use strict'; describe('scriptDirective', function() { var element; afterEach(function(){ dealoc(element); }); it('should populate $templateCache with contents of a ng-template script element', inject( function($compile, $templateCache) { $compile('
                      foo' + '' + '' + '
                      ' ); expect($templateCache.get('/myTemplate.html')).toBe('{{y}}'); expect($templateCache.get('/ignore')).toBeUndefined(); } )); it('should not compile scripts', inject(function($compile, $templateCache, $rootScope) { var doc = jqLite('
                      '); // jQuery is too smart and removes script tags doc[0].innerHTML = 'foo' + '' + ''; $compile(doc)($rootScope); $rootScope.$digest(); var scripts = doc.find('script'); expect(scripts.eq(0)[0].text).toBe('some {{binding}}'); expect(scripts.eq(1)[0].text).toBe('other {{binding}}'); dealoc(doc); })); }); angular.js-1.2.11/test/ng/directive/selectSpec.js000066400000000000000000001210221227375216300216370ustar00rootroot00000000000000'use strict'; describe('select', function() { var scope, formElement, element, $compile; function compile(html) { formElement = jqLite('
                      ' + html + '
                      '); element = formElement.find('select'); $compile(formElement)(scope); scope.$apply(); } beforeEach(inject(function($rootScope, _$compile_) { scope = $rootScope.$new(); //create a child scope because the root scope can't be $destroy-ed $compile = _$compile_; formElement = element = null; })); afterEach(function() { scope.$destroy(); //disables unknown option work during destruction dealoc(formElement); }); beforeEach(function() { this.addMatchers({ toEqualSelect: function(expected){ var actualValues = [], expectedValues = [].slice.call(arguments); forEach(this.actual.find('option'), function(option) { actualValues.push(option.selected ? [option.value] : option.value); }); this.message = function() { return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expectedValues) + '.'; }; return equals(expectedValues, actualValues); } }); }); describe('select-one', function() { it('should compile children of a select without a ngModel, but not create a model for it', function() { compile(''); scope.$apply(function() { scope.a = 'foo'; scope.b = 'bar'; }); expect(element.text()).toBe('foobarC'); }); it('should not interfere with selection via selected attr if ngModel directive is not present', function() { compile(''); expect(element).toEqualSelect('not me', ['me!'], 'nah'); }); it('should require', function() { compile( ''); scope.change = function() { scope.log += 'change;'; }; scope.$apply(function() { scope.log = ''; scope.selection = 'c'; }); expect(scope.form.select.$error.required).toBeFalsy(); expect(element).toBeValid(); expect(element).toBePristine(); scope.$apply(function() { scope.selection = ''; }); expect(scope.form.select.$error.required).toBeTruthy(); expect(element).toBeInvalid(); expect(element).toBePristine(); expect(scope.log).toEqual(''); element[0].value = 'c'; browserTrigger(element, 'change'); expect(element).toBeValid(); expect(element).toBeDirty(); expect(scope.log).toEqual('change;'); }); it('should not be invalid if no require', function() { compile( ''); expect(element).toBeValid(); expect(element).toBePristine(); }); it('should work with repeated value options', function() { scope.robots = ['c3p0', 'r2d2']; scope.robot = 'r2d2'; compile(''); expect(element).toEqualSelect('c3p0', ['r2d2']); browserTrigger(element.find('option').eq(0)); expect(element).toEqualSelect(['c3p0'], 'r2d2'); expect(scope.robot).toBe('c3p0'); scope.$apply(function() { scope.robots.unshift('wallee'); }); expect(element).toEqualSelect('wallee', ['c3p0'], 'r2d2'); expect(scope.robot).toBe('c3p0'); scope.$apply(function() { scope.robots = ['c3p0+', 'r2d2+']; scope.robot = 'r2d2+'; }); expect(element).toEqualSelect('c3p0+', ['r2d2+']); expect(scope.robot).toBe('r2d2+'); }); describe('empty option', function() { it('should select the empty option when model is undefined', function() { compile(''); expect(element).toEqualSelect([''], 'x', 'y'); }); it('should support defining an empty option anywhere in the option list', function() { compile(''); expect(element).toEqualSelect('x', [''], 'y'); }); it('should set the model to empty string when empty option is selected', function() { scope.robot = 'x'; compile(''); expect(element).toEqualSelect('', ['x'], 'y'); browserTrigger(element.find('option').eq(0)); expect(element).toEqualSelect([''], 'x', 'y'); expect(scope.robot).toBe(''); }); describe('interactions with repeated options', function() { it('should select empty option when model is undefined', function() { scope.robots = ['c3p0', 'r2d2']; compile(''); expect(element).toEqualSelect([''], 'c3p0', 'r2d2'); }); it('should set model to empty string when selected', function() { scope.robots = ['c3p0', 'r2d2']; compile(''); browserTrigger(element.find('option').eq(1)); expect(element).toEqualSelect('', ['c3p0'], 'r2d2'); expect(scope.robot).toBe('c3p0'); browserTrigger(element.find('option').eq(0)); expect(element).toEqualSelect([''], 'c3p0', 'r2d2'); expect(scope.robot).toBe(''); }); it('should not break if both the select and repeater models change at once', function() { scope.robots = ['c3p0', 'r2d2']; scope.robot = 'c3p0' compile(''); expect(element).toEqualSelect('', ['c3p0'], 'r2d2'); scope.$apply(function() { scope.robots = ['wallee']; scope.robot = ''; }); expect(element).toEqualSelect([''], 'wallee'); }); }); }); describe('unknown option', function() { it("should insert&select temporary unknown option when no options-model match", function() { compile(''); expect(element).toEqualSelect(['? undefined:undefined ?'], 'c3p0', 'r2d2'); scope.$apply(function() { scope.robot = 'r2d2'; }); expect(element).toEqualSelect('c3p0', ['r2d2']); scope.$apply(function() { scope.robot = "wallee"; }); expect(element).toEqualSelect(['? string:wallee ?'], 'c3p0', 'r2d2'); }); it("should NOT insert temporary unknown option when model is undefined and empty options " + "is present", function() { compile(''); expect(element).toEqualSelect([''], 'c3p0', 'r2d2'); expect(scope.robot).toBeUndefined(); scope.$apply(function() { scope.robot = null; }); expect(element).toEqualSelect(['? object:null ?'], '', 'c3p0', 'r2d2'); scope.$apply(function() { scope.robot = 'r2d2'; }); expect(element).toEqualSelect('', 'c3p0', ['r2d2']); scope.$apply(function() { delete scope.robot; }); expect(element).toEqualSelect([''], 'c3p0', 'r2d2'); }); it("should insert&select temporary unknown option when no options-model match, empty " + "option is present and model is defined", function() { scope.robot = 'wallee'; compile(''); expect(element).toEqualSelect(['? string:wallee ?'], '', 'c3p0', 'r2d2'); scope.$apply(function() { scope.robot = 'r2d2'; }); expect(element).toEqualSelect('', 'c3p0', ['r2d2']); }); describe('interactions with repeated options', function() { it('should work with repeated options', function() { compile(''); expect(element).toEqualSelect(['? undefined:undefined ?']); expect(scope.robot).toBeUndefined(); scope.$apply(function() { scope.robot = 'r2d2'; }); expect(element).toEqualSelect(['? string:r2d2 ?']); expect(scope.robot).toBe('r2d2'); scope.$apply(function() { scope.robots = ['c3p0', 'r2d2']; }); expect(element).toEqualSelect('c3p0', ['r2d2']); expect(scope.robot).toBe('r2d2'); }); it('should work with empty option and repeated options', function() { compile(''); expect(element).toEqualSelect(['']); expect(scope.robot).toBeUndefined(); scope.$apply(function() { scope.robot = 'r2d2'; }); expect(element).toEqualSelect(['? string:r2d2 ?'], ''); expect(scope.robot).toBe('r2d2'); scope.$apply(function() { scope.robots = ['c3p0', 'r2d2']; }); expect(element).toEqualSelect('', 'c3p0', ['r2d2']); expect(scope.robot).toBe('r2d2'); }); it('should insert unknown element when repeater shrinks and selected option is unavailable', function() { scope.robots = ['c3p0', 'r2d2']; scope.robot = 'r2d2'; compile(''); expect(element).toEqualSelect('c3p0', ['r2d2']); expect(scope.robot).toBe('r2d2'); scope.$apply(function() { scope.robots.pop(); }); expect(element).toEqualSelect(['? string:r2d2 ?'], 'c3p0'); expect(scope.robot).toBe('r2d2'); scope.$apply(function() { scope.robots.unshift('r2d2'); }); expect(element).toEqualSelect(['r2d2'], 'c3p0'); expect(scope.robot).toBe('r2d2'); scope.$apply(function() { delete scope.robots; }); expect(element).toEqualSelect(['? string:r2d2 ?']); expect(scope.robot).toBe('r2d2'); }); }); }); }); describe('select-multiple', function() { it('should support type="select-multiple"', function() { compile( ''); scope.$apply(function() { scope.selection = ['A']; }); expect(element).toEqualSelect(['A'], 'B'); scope.$apply(function() { scope.selection.push('B'); }); expect(element).toEqualSelect(['A'], ['B']); }); it('should work with optgroups', function() { compile(''); expect(element).toEqualSelect('A', 'B'); expect(scope.selection).toBeUndefined(); scope.$apply(function() { scope.selection = ['A']; }); expect(element).toEqualSelect(['A'], 'B'); scope.$apply(function() { scope.selection.push('B'); }); expect(element).toEqualSelect(['A'], ['B']); }); it('should require', function() { compile( ''); scope.$apply(function() { scope.selection = []; }); expect(scope.form.select.$error.required).toBeTruthy(); expect(element).toBeInvalid(); expect(element).toBePristine(); scope.$apply(function() { scope.selection = ['A']; }); expect(element).toBeValid(); expect(element).toBePristine(); element[0].value = 'B'; browserTrigger(element, 'change'); expect(element).toBeValid(); expect(element).toBeDirty(); }); }); describe('ngOptions', function() { function createSelect(attrs, blank, unknown) { var html = 'blank') : '') + (unknown ? (isString(unknown) ? unknown : '') : '') + ''; compile(html); } function createSingleSelect(blank, unknown) { createSelect({ 'ng-model':'selected', 'ng-options':'value.name for value in values' }, blank, unknown); } function createMultiSelect(blank, unknown) { createSelect({ 'ng-model':'selected', 'multiple':true, 'ng-options':'value.name for value in values' }, blank, unknown); } it('should throw when not formated "? for ? in ?"', function() { expect(function() { compile(''); }).toThrowMinErr('ngOptions', 'iexp', /Expected expression in form of/); }); it('should render a list', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; scope.selected = scope.values[0]; }); var options = element.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); it('should render zero as a valid display value', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 0}, {name: 1}, {name: 2}]; scope.selected = scope.values[0]; }); var options = element.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); it('should render an object', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'value as key for (key, value) in object' }); scope.$apply(function() { scope.object = {'red': 'FF0000', 'green': '00FF00', 'blue': '0000FF'}; scope.selected = scope.object.red; }); var options = element.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); expect(options[2].selected).toEqual(true); scope.$apply(function() { scope.object.azur = '8888FF'; }); options = element.find('option'); expect(options[3].selected).toEqual(true); }); it('should grow list', function() { createSingleSelect(); scope.$apply(function() { scope.values = []; }); expect(element.find('option').length).toEqual(1); // because we add special empty option expect(sortedHtml(element.find('option')[0])).toEqual(''); scope.$apply(function() { scope.values.push({name:'A'}); scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(1); expect(sortedHtml(element.find('option')[0])).toEqual(''); scope.$apply(function() { scope.values.push({name:'B'}); }); expect(element.find('option').length).toEqual(2); expect(sortedHtml(element.find('option')[0])).toEqual(''); expect(sortedHtml(element.find('option')[1])).toEqual(''); }); it('should shrink list', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(3); scope.$apply(function() { scope.values.pop(); }); expect(element.find('option').length).toEqual(2); expect(sortedHtml(element.find('option')[0])).toEqual(''); expect(sortedHtml(element.find('option')[1])).toEqual(''); scope.$apply(function() { scope.values.pop(); }); expect(element.find('option').length).toEqual(1); expect(sortedHtml(element.find('option')[0])).toEqual(''); scope.$apply(function() { scope.values.pop(); scope.selected = null; }); expect(element.find('option').length).toEqual(1); // we add back the special empty option }); it('should shrink and then grow list', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(3); scope.$apply(function() { scope.values = [{name: '1'}, {name: '2'}]; scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(2); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(3); }); it('should update list', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}]; scope.selected = scope.values[0]; }); scope.$apply(function() { scope.values = [{name: 'B'}, {name: 'C'}, {name: 'D'}]; scope.selected = scope.values[0]; }); var options = element.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual(''); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); it('should preserve existing options', function() { createSingleSelect(true); scope.$apply(function() { scope.values = []; }); expect(element.find('option').length).toEqual(1); scope.$apply(function() { scope.values = [{name: 'A'}]; scope.selected = scope.values[0]; }); expect(element.find('option').length).toEqual(2); expect(jqLite(element.find('option')[0]).text()).toEqual('blank'); expect(jqLite(element.find('option')[1]).text()).toEqual('A'); scope.$apply(function() { scope.values = []; scope.selected = null; }); expect(element.find('option').length).toEqual(1); expect(jqLite(element.find('option')[0]).text()).toEqual('blank'); }); it('should ignore $ and $$ properties', function() { createSelect({ 'ng-options': 'key as value for (key, value) in object', 'ng-model': 'selected' }); scope.$apply(function() { scope.object = {'regularProperty': 'visible', '$$private': 'invisible', '$property': 'invisible'}; scope.selected = 'regularProperty'; }); var options = element.find('option'); expect(options.length).toEqual(1); expect(sortedHtml(options[0])).toEqual(''); }); it('should allow expressions over multiple lines', function() { scope.isNotFoo = function(item) { return item.name !== 'Foo'; }; createSelect({ 'ng-options': 'key.id\n' + 'for key in object\n' + '| filter:isNotFoo', 'ng-model': 'selected' }); scope.$apply(function() { scope.object = [{'id': 1, 'name': 'Foo'}, {'id': 2, 'name': 'Bar'}, {'id': 3, 'name': 'Baz'}]; scope.selected = scope.object[0]; }); var options = element.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[1])).toEqual(''); expect(sortedHtml(options[2])).toEqual(''); }); describe('binding', function() { it('should bind to scope value', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); scope.$apply(function() { scope.selected = scope.values[1]; }); expect(element.val()).toEqual('1'); }); it('should bind to scope value and group', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'item.name group by item.group for item in values' }); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B', group: 'first'}, {name: 'C', group: 'second'}, {name: 'D', group: 'first'}, {name: 'E', group: 'second'}]; scope.selected = scope.values[3]; }); expect(element.val()).toEqual('3'); var first = jqLite(element.find('optgroup')[0]); var b = jqLite(first.find('option')[0]); var d = jqLite(first.find('option')[1]); expect(first.attr('label')).toEqual('first'); expect(b.text()).toEqual('B'); expect(d.text()).toEqual('D'); var second = jqLite(element.find('optgroup')[1]); var c = jqLite(second.find('option')[0]); var e = jqLite(second.find('option')[1]); expect(second.attr('label')).toEqual('second'); expect(c.text()).toEqual('C'); expect(e.text()).toEqual('E'); scope.$apply(function() { scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); }); it('should bind to scope value and track/identify objects', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'item as item.name for item in values track by item.id' }); scope.$apply(function() { scope.values = [{id: 1, name: 'first'}, {id: 2, name: 'second'}, {id: 3, name: 'third'}, {id: 4, name: 'forth'}]; scope.selected = {id: 2}; }); expect(element.val()).toEqual('2'); var first = jqLite(element.find('option')[0]); expect(first.text()).toEqual('first'); expect(first.attr('value')).toEqual('1'); var forth = jqLite(element.find('option')[3]); expect(forth.text()).toEqual('forth'); expect(forth.attr('value')).toEqual('4'); scope.$apply(function() { scope.selected = scope.values[3]; }); expect(element.val()).toEqual('4'); }); it('should bind to scope value through experession', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'item.id as item.name for item in values' }); scope.$apply(function() { scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}]; scope.selected = scope.values[0].id; }); expect(element.val()).toEqual('0'); scope.$apply(function() { scope.selected = scope.values[1].id; }); expect(element.val()).toEqual('1'); }); it('should bind to object key', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'key as value for (key, value) in object' }); scope.$apply(function() { scope.object = {red: 'FF0000', green: '00FF00', blue: '0000FF'}; scope.selected = 'green'; }); expect(element.val()).toEqual('green'); scope.$apply(function() { scope.selected = 'blue'; }); expect(element.val()).toEqual('blue'); }); it('should bind to object value', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'value as key for (key, value) in object' }); scope.$apply(function() { scope.object = {red: 'FF0000', green: '00FF00', blue:'0000FF'}; scope.selected = '00FF00'; }); expect(element.val()).toEqual('green'); scope.$apply(function() { scope.selected = '0000FF'; }); expect(element.val()).toEqual('blue'); }); it('should insert a blank option if bound to null', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}]; scope.selected = null; }); expect(element.find('option').length).toEqual(2); expect(element.val()).toEqual(''); expect(jqLite(element.find('option')[0]).val()).toEqual(''); scope.$apply(function() { scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); expect(element.find('option').length).toEqual(1); }); it('should reuse blank option if bound to null', function() { createSingleSelect(true); scope.$apply(function() { scope.values = [{name: 'A'}]; scope.selected = null; }); expect(element.find('option').length).toEqual(2); expect(element.val()).toEqual(''); expect(jqLite(element.find('option')[0]).val()).toEqual(''); scope.$apply(function() { scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); expect(element.find('option').length).toEqual(2); }); it('should insert a unknown option if bound to something not in the list', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}]; scope.selected = {}; }); expect(element.find('option').length).toEqual(2); expect(element.val()).toEqual('?'); expect(jqLite(element.find('option')[0]).val()).toEqual('?'); scope.$apply(function() { scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); expect(element.find('option').length).toEqual(1); }); it('should select correct input if previously selected option was "?"', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = {}; }); expect(element.find('option').length).toEqual(3); expect(element.val()).toEqual('?'); expect(element.find('option').eq(0).val()).toEqual('?'); browserTrigger(element.find('option').eq(1)); expect(element.val()).toEqual('0'); expect(element.find('option').eq(0).prop('selected')).toBeTruthy(); expect(element.find('option').length).toEqual(2); }); }); describe('blank option', function () { it('should be compiled as template, be watched and updated', function () { var option; createSingleSelect(''); scope.$apply(function() { scope.blankVal = 'so blank'; scope.values = [{name: 'A'}]; }); // check blank option is first and is compiled expect(element.find('option').length).toBe(2); option = element.find('option').eq(0); expect(option.val()).toBe(''); expect(option.text()).toBe('blank is so blank'); scope.$apply(function() { scope.blankVal = 'not so blank'; }); // check blank option is first and is compiled expect(element.find('option').length).toBe(2); option = element.find('option').eq(0); expect(option.val()).toBe(''); expect(option.text()).toBe('blank is not so blank'); }); it('should support binding via ngBindTemplate directive', function () { var option; createSingleSelect(''); scope.$apply(function() { scope.blankVal = 'so blank'; scope.values = [{name: 'A'}]; }); // check blank option is first and is compiled expect(element.find('option').length).toBe(2); option = element.find('option').eq(0); expect(option.val()).toBe(''); expect(option.text()).toBe('blank is so blank'); }); it('should support biding via ngBind attribute', function () { var option; createSingleSelect(''); scope.$apply(function() { scope.blankVal = 'is blank'; scope.values = [{name: 'A'}]; }); // check blank option is first and is compiled expect(element.find('option').length).toBe(2); option = element.find('option').eq(0); expect(option.val()).toBe(''); expect(option.text()).toBe('is blank'); }); it('should be rendered with the attributes preserved', function () { var option; createSingleSelect(''); scope.$apply(function() { scope.blankVal = 'is blank'; }); // check blank option is first and is compiled option = element.find('option').eq(0); expect(option.hasClass('coyote')).toBeTruthy(); expect(option.attr('id')).toBe('road-runner'); expect(option.attr('custom-attr')).toBe('custom-attr'); }); it('should be selected, if it is available and no other option is selected', function() { // selectedIndex is used here because jqLite incorrectly reports element.val() scope.$apply(function() { scope.values = [{name: 'A'}]; }); createSingleSelect(true); // ensure the first option (the blank option) is selected expect(element[0].selectedIndex).toEqual(0); scope.$digest(); // ensure the option has not changed following the digest expect(element[0].selectedIndex).toEqual(0); }); }); describe('on change', function() { it('should update model on change', function() { createSingleSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = scope.values[0]; }); expect(element.val()).toEqual('0'); element.val('1'); browserTrigger(element, 'change'); expect(scope.selected).toEqual(scope.values[1]); }); it('should update model on change through expression', function() { createSelect({ 'ng-model': 'selected', 'ng-options': 'item.id as item.name for item in values' }); scope.$apply(function() { scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}]; scope.selected = scope.values[0].id; }); expect(element.val()).toEqual('0'); element.val('1'); browserTrigger(element, 'change'); expect(scope.selected).toEqual(scope.values[1].id); }); it('should update model to null on change', function() { createSingleSelect(true); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = scope.values[0]; element.val('0'); }); element.val(''); browserTrigger(element, 'change'); expect(scope.selected).toEqual(null); }); }); describe('select-many', function() { it('should read multiple selection', function() { createMultiSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = []; }); expect(element.find('option').length).toEqual(2); expect(element.find('option')[0].selected).toBeFalsy(); expect(element.find('option')[1].selected).toBeFalsy(); scope.$apply(function() { scope.selected.push(scope.values[1]); }); expect(element.find('option').length).toEqual(2); expect(element.find('option')[0].selected).toBeFalsy(); expect(element.find('option')[1].selected).toBeTruthy(); scope.$apply(function() { scope.selected.push(scope.values[0]); }); expect(element.find('option').length).toEqual(2); expect(element.find('option')[0].selected).toBeTruthy(); expect(element.find('option')[1].selected).toBeTruthy(); }); it('should update model on change', function() { createMultiSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = []; }); element.find('option')[0].selected = true; browserTrigger(element, 'change'); expect(scope.selected).toEqual([scope.values[0]]); }); it('should select from object', function() { createSelect({ 'ng-model':'selected', 'multiple':true, 'ng-options':'key as value for (key,value) in values' }); scope.values = {'0':'A', '1':'B'}; scope.selected = ['1']; scope.$digest(); expect(element.find('option')[1].selected).toBe(true); element.find('option')[0].selected = true; browserTrigger(element, 'change'); expect(scope.selected).toEqual(['0', '1']); element.find('option')[1].selected = false; browserTrigger(element, 'change'); expect(scope.selected).toEqual(['0']); }); it('should deselect all options when model is emptied', function() { createMultiSelect(); scope.$apply(function() { scope.values = [{name: 'A'}, {name: 'B'}]; scope.selected = [scope.values[0]]; }); expect(element.find('option')[0].selected).toEqual(true); scope.$apply(function() { scope.selected.pop(); }); expect(element.find('option')[0].selected).toEqual(false); }) }); describe('ngRequired', function() { it('should allow bindings on ngRequired', function() { createSelect({ 'ng-model': 'value', 'ng-options': 'item.name for item in values', 'ng-required': 'required' }, true); scope.$apply(function() { scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}]; scope.required = false; }); element.val(''); browserTrigger(element, 'change'); expect(element).toBeValid(); scope.$apply(function() { scope.required = true; }); expect(element).toBeInvalid(); scope.$apply(function() { scope.value = scope.values[0]; }); expect(element).toBeValid(); element.val(''); browserTrigger(element, 'change'); expect(element).toBeInvalid(); scope.$apply(function() { scope.required = false; }); expect(element).toBeValid(); }); it('should treat an empty array as invalid when `multiple` attribute used', function() { createSelect({ 'ng-model': 'value', 'ng-options': 'item.name for item in values', 'ng-required': 'required', 'multiple': '' }, true); scope.$apply(function() { scope.value = []; scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}]; scope.required = true; }); expect(element).toBeInvalid(); scope.$apply(function() { // ngModelWatch does not set objectEquality flag // array must be replaced in order to trigger $formatters scope.value = [scope.values[0]]; }); expect(element).toBeValid(); }); it('should allow falsy values as values', function() { createSelect({ 'ng-model': 'value', 'ng-options': 'item.value as item.name for item in values', 'ng-required': 'required' }, true); scope.$apply(function() { scope.values = [{name: 'True', value: true}, {name: 'False', value: false}]; scope.required = false; }); element.val('1'); browserTrigger(element, 'change'); expect(element).toBeValid(); expect(scope.value).toBe(false); scope.$apply(function() { scope.required = true; }); expect(element).toBeValid(); expect(scope.value).toBe(false); }); }); }); describe('option', function() { it('should populate value attribute on OPTION', function() { compile(''); expect(element).toEqualSelect(['? undefined:undefined ?'], 'abc'); }); it('should ignore value if already exists', function() { compile(''); expect(element).toEqualSelect(['? undefined:undefined ?'], 'abc'); }); it('should set value even if self closing HTML', function() { scope.x = 'hello' compile(''); expect(element).toEqualSelect(['hello']); }); it('should not blow up when option directive is found inside of a datalist', inject(function($compile, $rootScope) { var element = $compile('
                      ' + '' + '{{foo}}' + '
                      ')($rootScope); $rootScope.foo = 'success'; $rootScope.$digest(); expect(element.find('span').text()).toBe('success'); dealoc(element); })); it('should throw an exception if an option value interpolates to "hasOwnProperty"', function() { scope.hasOwnPropertyOption = "hasOwnProperty"; expect(function() { compile(''); }).toThrowMinErr('ng','badname', 'hasOwnProperty is not a valid "option value" name'); }); }); }); angular.js-1.2.11/test/ng/directive/styleSpec.js000066400000000000000000000014141227375216300215220ustar00rootroot00000000000000'use strict'; describe('style', function() { var element; afterEach(function() { dealoc(element); }); it('should not compile style element', inject(function($compile, $rootScope) { element = jqLite(''); $compile(element)($rootScope); $rootScope.$digest(); // read innerHTML and trim to pass on IE8 expect(trim(element[0].innerHTML)).toBe('should {{notBound}}'); })); it('should compile content of element with style attr', inject(function($compile, $rootScope) { element = jqLite('
                      {{bind}}
                      '); $compile(element)($rootScope); $rootScope.$apply(function() { $rootScope.bind = 'value'; }); expect(element.text()).toBe('value'); })); }); angular.js-1.2.11/test/ng/documentSpec.js000066400000000000000000000002551227375216300202240ustar00rootroot00000000000000'use strict'; describe('$document', function() { it("should inject $document", inject(function($document) { expect($document).toEqual(jqLite(document)); })); }); angular.js-1.2.11/test/ng/exceptionHandlerSpec.js000066400000000000000000000014171227375216300217030ustar00rootroot00000000000000'use strict'; describe('$exceptionHandler', function() { it('should log errors with single argument', function() { module(function($provide){ $provide.provider('$exceptionHandler', $ExceptionHandlerProvider); }); inject(function($log, $exceptionHandler) { $exceptionHandler('myError'); expect($log.error.logs.shift()).toEqual(['myError']); }); }); it('should log errors with multiple arguments', function() { module(function($provide){ $provide.provider('$exceptionHandler', $ExceptionHandlerProvider); }); inject(function($log, $exceptionHandler) { $exceptionHandler('myError', 'comment'); expect($log.error.logs.shift()).toEqual(['myError', 'comment']); }); }); }); angular.js-1.2.11/test/ng/filter/000077500000000000000000000000001227375216300165205ustar00rootroot00000000000000angular.js-1.2.11/test/ng/filter/filterSpec.js000066400000000000000000000117641227375216300211670ustar00rootroot00000000000000'use strict'; describe('Filter: filter', function() { var filter; beforeEach(inject(function($filter){ filter = $filter('filter'); })); it('should filter by string', function() { var items = ['MIsKO', {name: 'shyam'}, ['adam'], 1234]; expect(filter(items, '').length).toBe(4); expect(filter(items, undefined).length).toBe(4); expect(filter(items, 'iSk').length).toBe(1); expect(filter(items, 'isk')[0]).toBe('MIsKO'); expect(filter(items, 'yam').length).toBe(1); expect(filter(items, 'yam')[0]).toEqual(items[1]); expect(filter(items, 'da').length).toBe(1); expect(filter(items, 'da')[0]).toEqual(items[2]); expect(filter(items, '34').length).toBe(1); expect(filter(items, '34')[0]).toBe(1234); expect(filter(items, "I don't exist").length).toBe(0); }); it('should not read $ properties', function() { expect(''.charAt(0)).toBe(''); // assumption var items = [{$name: 'misko'}]; expect(filter(items, 'misko').length).toBe(0); }); it('should filter on specific property', function() { var items = [{ignore: 'a', name: 'a'}, {ignore: 'a', name: 'abc'}]; expect(filter(items, {}).length).toBe(2); expect(filter(items, {name: 'a'}).length).toBe(2); expect(filter(items, {name: 'b'}).length).toBe(1); expect(filter(items, {name: 'b'})[0].name).toBe('abc'); }); it('should take function as predicate', function() { var items = [{name: 'a'}, {name: 'abc', done: true}]; expect(filter(items, function(i) {return i.done;}).length).toBe(1); }); it('should take object as perdicate', function() { var items = [{first: 'misko', last: 'hevery'}, {first: 'adam', last: 'abrons'}]; expect(filter(items, {first:'', last:''}).length).toBe(2); expect(filter(items, {first:'', last:'hevery'}).length).toBe(1); expect(filter(items, {first:'adam', last:'hevery'}).length).toBe(0); expect(filter(items, {first:'misko', last:'hevery'}).length).toBe(1); expect(filter(items, {first:'misko', last:'hevery'})[0]).toEqual(items[0]); }); it('should support predicat object with dots in the name', function() { var items = [{'first.name': 'misko', 'last.name': 'hevery'}, {'first.name': 'adam', 'last.name': 'abrons'}]; expect(filter(items, {'first.name':'', 'last.name':''}).length).toBe(2); expect(filter(items, {'first.name':'misko', 'last.name':''})).toEqual([items[0]]); }); it('should match any properties for given "$" property', function() { var items = [{first: 'tom', last: 'hevery'}, {first: 'adam', last: 'hevery', alias: 'tom', done: false}, {first: 'john', last: 'clark', middle: 'tommy'}]; expect(filter(items, {$: 'tom'}).length).toBe(3); expect(filter(items, {$: 'a'}).length).toBe(2); expect(filter(items, {$: false}).length).toBe(1); expect(filter(items, {$: 10}).length).toBe(0); expect(filter(items, {$: 'hevery'})[0]).toEqual(items[0]); }); it('should support boolean properties', function() { var items = [{name: 'tom', current: true}, {name: 'demi', current: false}, {name: 'sofia'}]; expect(filter(items, {current:true}).length).toBe(1); expect(filter(items, {current:true})[0].name).toBe('tom'); expect(filter(items, {current:false}).length).toBe(1); expect(filter(items, {current:false})[0].name).toBe('demi'); }); it('should support negation operator', function() { var items = ['misko', 'adam']; expect(filter(items, '!isk').length).toBe(1); expect(filter(items, '!isk')[0]).toEqual(items[1]); }); describe('should support comparator', function() { it('as equality when true', function() { var items = ['misko', 'adam', 'adamson']; var expr = 'adam'; expect(filter(items, expr, true)).toEqual([items[1]]); expect(filter(items, expr, false)).toEqual([items[1], items[2]]); var items = [ {key: 'value1', nonkey: 1}, {key: 'value2', nonkey: 2}, {key: 'value12', nonkey: 3}, {key: 'value1', nonkey:4}, {key: 'Value1', nonkey:5} ]; var expr = {key: 'value1'}; expect(filter(items, expr, true)).toEqual([items[0], items[3]]); var items = [ {key: 1, nonkey: 1}, {key: 2, nonkey: 2}, {key: 12, nonkey: 3}, {key: 1, nonkey:4} ]; var expr = { key: 1 }; expect(filter(items, expr, true)).toEqual([items[0], items[3]]); var expr = 12; expect(filter(items, expr, true)).toEqual([items[2]]); }); it('and use the function given to compare values', function() { var items = [ {key: 1, nonkey: 1}, {key: 2, nonkey: 2}, {key: 12, nonkey: 3}, {key: 1, nonkey:14} ]; var expr = {key: 10}; var comparator = function (obj,value) { return obj > value; } expect(filter(items, expr, comparator)).toEqual([items[2]]); expr = 10; expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]); }); }); }); angular.js-1.2.11/test/ng/filter/filtersSpec.js000066400000000000000000000322571227375216300213520ustar00rootroot00000000000000'use strict'; describe('filters', function() { var filter; beforeEach(inject(function($filter){ filter = $filter; })); it('should call the filter when evaluating expression', function(){ var filter = jasmine.createSpy('myFilter'); createInjector(['ng', function($filterProvider) { $filterProvider.register('myFilter', valueFn(filter)); }]).invoke(function($rootScope) { $rootScope.$eval('10|myFilter'); }); expect(filter).toHaveBeenCalledWith(10); }); describe('formatNumber', function() { var pattern; beforeEach(function() { pattern = { minInt: 1, minFrac: 0, maxFrac: 3, posPre: '', posSuf: '', negPre: '-', negSuf: '', gSize: 3, lgSize: 3 }; }); it('should format according to different patterns', function() { pattern.gSize = 2; var num = formatNumber(1234567.89, pattern, ',', '.'); expect(num).toBe('12,34,567.89'); num = formatNumber(1234.56, pattern, ',', '.'); expect(num).toBe('1,234.56'); pattern.negPre = '('; pattern.negSuf = '-)'; num = formatNumber(-1234, pattern, ',', '.'); expect(num).toBe('(1,234-)'); pattern.posPre = '+'; pattern.posSuf = '+'; num = formatNumber(1234, pattern, ',', '.'); expect(num).toBe('+1,234+'); pattern.posPre = pattern.posSuf = ''; pattern.minFrac = 2; num = formatNumber(1, pattern, ',', '.'); expect(num).toBe('1.00'); pattern.maxFrac = 4; num = formatNumber(1.11119, pattern, ',', '.'); expect(num).toBe('1.1112'); }); it('should format according different seperators', function() { var num = formatNumber(1234567.1, pattern, '.', ',', 2); expect(num).toBe('1.234.567,10'); }); it('should format with or without fractionSize', function() { var num = formatNumber(123.1, pattern, ',', '.', 3); expect(num).toBe('123.100'); num = formatNumber(123.12, pattern, ',', '.'); expect(num).toBe('123.12'); var num = formatNumber(123.1116, pattern, ',', '.'); expect(num).toBe('123.112'); }); it('should format the same with string as well as numeric fractionSize', function(){ var num = formatNumber(123.1, pattern, ',', '.', "0"); expect(num).toBe('123'); var num = formatNumber(123.1, pattern, ',', '.', 0); expect(num).toBe('123'); var num = formatNumber(123.1, pattern, ',', '.', "3"); expect(num).toBe('123.100'); var num = formatNumber(123.1, pattern, ',', '.', 3); expect(num).toBe('123.100'); }); }); describe('currency', function() { var currency; beforeEach(function() { currency = filter('currency'); }); it('should do basic currency filtering', function() { expect(currency(0)).toEqual('$0.00'); expect(currency(-999)).toEqual('($999.00)'); expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57'); }); it('should return empty string for non-numbers', function() { expect(currency()).toBe(''); expect(currency('abc')).toBe(''); }); it('should handle zero and nearly-zero values properly', function() { // This expression is known to yield 4.440892098500626e-16 instead of 0.0. expect(currency(1.07 + 1 - 2.07)).toBe('$0.00'); expect(currency(0.008)).toBe('$0.01'); expect(currency(0.003)).toBe('$0.00'); }); }); describe('number', function() { var number; beforeEach(inject(function($rootScope) { number = filter('number'); })); it('should do basic filter', function() { expect(number(0, 0)).toEqual('0'); expect(number(-999)).toEqual('-999'); expect(number(123)).toEqual('123'); expect(number(1234567)).toEqual('1,234,567'); expect(number(1234)).toEqual('1,234'); expect(number(1234.5678)).toEqual('1,234.568'); expect(number(Number.NaN)).toEqual(''); expect(number("1234.5678")).toEqual('1,234.568'); expect(number(1/0)).toEqual(""); expect(number(1, 2)).toEqual("1.00"); expect(number(.1, 2)).toEqual("0.10"); expect(number(.01, 2)).toEqual("0.01"); expect(number(.001, 3)).toEqual("0.001"); expect(number(.0001, 3)).toEqual("0.000"); expect(number(9, 2)).toEqual("9.00"); expect(number(.9, 2)).toEqual("0.90"); expect(number(.99, 2)).toEqual("0.99"); expect(number(.999, 3)).toEqual("0.999"); expect(number(.9999, 3)).toEqual("1.000"); expect(number(1234.567, 0)).toEqual("1,235"); expect(number(1234.567, 1)).toEqual("1,234.6"); expect(number(1234.567, 2)).toEqual("1,234.57"); }); it('should filter exponentially large numbers', function() { expect(number(1e50)).toEqual('1e+50'); expect(number(-2e100)).toEqual('-2e+100'); }); it('should ignore fraction sizes for large numbers', function() { expect(number(1e50, 2)).toEqual('1e+50'); expect(number(-2e100, 5)).toEqual('-2e+100'); }); it('should filter exponentially small numbers', function() { expect(number(1e-50, 0)).toEqual('0'); expect(number(1e-6, 6)).toEqual('0.000001'); expect(number(1e-7, 6)).toEqual('0.000000'); expect(number(-1e-50, 0)).toEqual('-0'); expect(number(-1e-6, 6)).toEqual('-0.000001'); expect(number(-1e-7, 6)).toEqual('-0.000000'); }); }); describe('json', function () { it('should do basic filter', function() { expect(filter('json')({a:"b"})).toEqual(toJson({a:"b"}, true)); }); }); describe('lowercase', function() { it('should do basic filter', function() { expect(filter('lowercase')('AbC')).toEqual('abc'); expect(filter('lowercase')(null)).toBeNull(); }); }); describe('uppercase', function() { it('should do basic filter', function() { expect(filter('uppercase')('AbC')).toEqual('ABC'); expect(filter('uppercase')(null)).toBeNull(); }); }); describe('date', function() { var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z'); var date; beforeEach(inject(function($filter) { date = $filter('date'); })); it('should ignore falsy inputs', function() { expect(date(null)).toBeNull(); expect(date('')).toEqual(''); }); it('should do basic filter', function() { expect(date(noon)).toEqual(date(noon, 'mediumDate')); expect(date(noon, '')).toEqual(date(noon, 'mediumDate')); }); it('should accept number or number string representing milliseconds as input', function() { expect(date(noon.getTime())).toEqual(date(noon.getTime(), 'mediumDate')); expect(date(noon.getTime() + "")).toEqual(date(noon.getTime() + "", 'mediumDate')); }); it('should accept various format strings', function() { expect(date(morning, "yy-MM-dd HH:mm:ss")). toEqual('10-09-03 07:05:08'); expect(date(morning, "yy-MM-dd HH:mm:ss.sss")). toEqual('10-09-03 07:05:08.001'); expect(date(midnight, "yyyy-M-d h=H:m:saZ")). toEqual('2010-9-3 12=0:5:8AM-0500'); expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")). toEqual('2010-09-03 12=00:05:08AM-0500'); expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")). toEqual('2010-09-03 12=00:05:08.123AM-0500'); expect(date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")). toEqual('2010-09-03 12=12:05:08PM-0500'); expect(date(noon, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")). toEqual('2010-09-03 12=12:05:08.012PM-0500'); expect(date(noon, "EEE, MMM d, yyyy")). toEqual('Fri, Sep 3, 2010'); expect(date(noon, "EEEE, MMMM dd, yyyy")). toEqual('Friday, September 03, 2010'); expect(date(earlyDate, "MMMM dd, y")). toEqual('September 03, 1'); }); it('should accept negative numbers as strings', function() { //Note: this tests a timestamp set for 3 days before the unix epoch. //The behavior of `date` depends on your timezone, which is why we check just //the year and not the whole daye. See Issue #4218 expect(date('-259200000').split(' ')[2]).toEqual('1969'); }); it('should format timezones correctly (as per ISO_8601)', function() { //Note: TzDate's first argument is offset, _not_ timezone. var utc = new angular.mock.TzDate( 0, '2010-09-03T12:05:08.000Z'); var eastOfUTC = new angular.mock.TzDate(-5, '2010-09-03T12:05:08.000Z'); var westOfUTC = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); var eastOfUTCPartial = new angular.mock.TzDate(-5.5, '2010-09-03T12:05:08.000Z'); var westOfUTCPartial = new angular.mock.TzDate(+5.5, '2010-09-03T12:05:08.000Z'); expect(date(utc, "yyyy-MM-ddTHH:mm:ssZ")). toEqual('2010-09-03T12:05:08+0000') expect(date(eastOfUTC, "yyyy-MM-ddTHH:mm:ssZ")). toEqual('2010-09-03T17:05:08+0500') expect(date(westOfUTC, "yyyy-MM-ddTHH:mm:ssZ")). toEqual('2010-09-03T07:05:08-0500') expect(date(eastOfUTCPartial, "yyyy-MM-ddTHH:mm:ssZ")). toEqual('2010-09-03T17:35:08+0530') expect(date(westOfUTCPartial, "yyyy-MM-ddTHH:mm:ssZ")). toEqual('2010-09-03T06:35:08-0530') }); it('should treat single quoted strings as string literals', function() { expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")). toEqual('2010de axdd adZ 12=0:5:8AM-0500'); }); it('should treat a sequence of two single quotes as a literal single quote', function() { expect(date(midnight, "yyyy'de' 'a''dd' 'adZ' h=H:m:saZ")). toEqual("2010de a'dd adZ 12=0:5:8AM-0500"); }); it('should accept default formats', function() { expect(date(noon, "medium")). toEqual('Sep 3, 2010 12:05:08 PM'); expect(date(noon, "short")). toEqual('9/3/10 12:05 PM'); expect(date(noon, "fullDate")). toEqual('Friday, September 3, 2010'); expect(date(noon, "longDate")). toEqual('September 3, 2010'); expect(date(noon, "mediumDate")). toEqual('Sep 3, 2010'); expect(date(noon, "shortDate")). toEqual('9/3/10'); expect(date(noon, "mediumTime")). toEqual('12:05:08 PM'); expect(date(noon, "shortTime")). toEqual('12:05 PM'); }); it('should parse format ending with non-replaced string', function() { expect(date(morning, 'yy/xxx')).toEqual('10/xxx'); }); it('should support various iso8061 date strings with timezone as input', function() { var format = 'yyyy-MM-dd ss'; var localDay = new Date(Date.UTC(2003, 9, 10, 13, 2, 3, 0)).getDate(); //full ISO8061 expect(date('2003-09-10T13:02:03.000Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.000+00:00', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('20030910T033203-0930', format)).toEqual('2003-09-' + localDay + ' 03'); //no millis expect(date('2003-09-10T13:02:03Z', format)).toEqual('2003-09-' + localDay + ' 03'); //no seconds expect(date('2003-09-10T13:02Z', format)).toEqual('2003-09-' + localDay + ' 00'); //no minutes expect(date('2003-09-10T13Z', format)).toEqual('2003-09-' + localDay + ' 00'); }); it('should parse iso8061 date strings without timezone as local time', function() { var format = 'yyyy-MM-dd HH-mm-ss'; //full ISO8061 without timezone expect(date('2003-09-10T03:02:04.000', format)).toEqual('2003-09-10 03-02-04'); expect(date('20030910T030204', format)).toEqual('2003-09-10 03-02-04'); //no time expect(date('2003-09-10', format)).toEqual('2003-09-10 00-00-00'); }); it('should support different degrees of subsecond precision', function () { var format = 'yyyy-MM-dd ss'; var localDay = new Date(Date.UTC(2003, 9-1, 10, 13, 2, 3, 123)).getDate(); expect(date('2003-09-10T13:02:03.12345678Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.1234567Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.123456Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.12345Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.1234Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.123Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.12Z', format)).toEqual('2003-09-' + localDay + ' 03'); expect(date('2003-09-10T13:02:03.1Z', format)).toEqual('2003-09-' + localDay + ' 03'); }); }); }); angular.js-1.2.11/test/ng/filter/limitToSpec.js000066400000000000000000000044111227375216300213120ustar00rootroot00000000000000'use strict'; describe('Filter: limitTo', function() { var items; var str var limitTo; beforeEach(inject(function($filter) { items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; str = "tuvwxyz"; limitTo = $filter('limitTo'); })); it('should return the first X items when X is positive', function() { expect(limitTo(items, 3)).toEqual(['a', 'b', 'c']); expect(limitTo(items, '3')).toEqual(['a', 'b', 'c']); expect(limitTo(str, 3)).toEqual("tuv"); expect(limitTo(str, '3')).toEqual("tuv"); }); it('should return the last X items when X is negative', function() { expect(limitTo(items, -3)).toEqual(['f', 'g', 'h']); expect(limitTo(items, '-3')).toEqual(['f', 'g', 'h']); expect(limitTo(str, -3)).toEqual("xyz"); expect(limitTo(str, '-3')).toEqual("xyz"); }); it('should return an empty array when X cannot be parsed', function() { expect(limitTo(items, 'bogus')).toEqual([]); expect(limitTo(items, 'null')).toEqual([]); expect(limitTo(items, 'undefined')).toEqual([]); expect(limitTo(items, null)).toEqual([]); expect(limitTo(items, undefined)).toEqual([]); }); it('should return an empty string when X cannot be parsed', function() { expect(limitTo(str, 'bogus')).toEqual(""); expect(limitTo(str, 'null')).toEqual(""); expect(limitTo(str, 'undefined')).toEqual(""); expect(limitTo(str, null)).toEqual(""); expect(limitTo(str, undefined)).toEqual(""); }); it('should return input if not String or Array', function() { expect(limitTo(1,1)).toEqual(1); expect(limitTo(null, 1)).toEqual(null); expect(limitTo(undefined, 1)).toEqual(undefined); expect(limitTo({}, 1)).toEqual({}); }); it('should return a copy of input array if X is exceeds array length', function () { expect(limitTo(items, 9)).toEqual(items); expect(limitTo(items, '9')).toEqual(items); expect(limitTo(items, -9)).toEqual(items); expect(limitTo(items, '-9')).toEqual(items); expect(limitTo(items, 9)).not.toBe(items); }); it('should return the entire string if X exceeds input length', function() { expect(limitTo(str, 9)).toEqual(str); expect(limitTo(str, '9')).toEqual(str); expect(limitTo(str, -9)).toEqual(str); expect(limitTo(str, '-9')).toEqual(str); }) }); angular.js-1.2.11/test/ng/filter/orderBySpec.js000066400000000000000000000021671227375216300213050ustar00rootroot00000000000000'use strict'; describe('Filter: orderBy', function() { var orderBy; beforeEach(inject(function($filter) { orderBy = $filter('orderBy'); })); it('should return same array if predicate is falsy', function() { var array = [1, 2, 3]; expect(orderBy(array)).toBe(array); }); it('shouldSortArrayInReverse', function() { expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]); expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]); expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]); }); it('should sort array by predicate', function() { expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]); expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]); expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]); }); it('should use function', function() { expect( orderBy( [{a:15, b:1},{a:2, b:1}], function(value) { return value.a; })). toEqual([{a:2, b:1},{a:15, b:1}]); }); }); angular.js-1.2.11/test/ng/filterSpec.js000066400000000000000000000021061227375216300176700ustar00rootroot00000000000000'use strict'; describe('$filter', function() { var $filterProvider, $filter; beforeEach(module(function(_$filterProvider_) { $filterProvider = _$filterProvider_; })); beforeEach(inject(function(_$filter_) { $filter = _$filter_; })); describe('provider', function() { it('should allow registration of filters', function() { var FooFilter = function() { return function() { return 'foo'; }; }; $filterProvider.register('foo', FooFilter); var fooFilter = $filter('foo'); expect(fooFilter()).toBe('foo'); }); it('should allow registration of a map of filters', function() { var FooFilter = function() { return function() { return 'foo'; }; }; var BarFilter = function() { return function() { return 'bar'; }; }; $filterProvider.register({ 'foo': FooFilter, 'bar': BarFilter }); var fooFilter = $filter('foo'); expect(fooFilter()).toBe('foo'); var barFilter = $filter('bar'); expect(barFilter()).toBe('bar'); }); }); });angular.js-1.2.11/test/ng/httpBackendSpec.js000066400000000000000000000366461227375216300206520ustar00rootroot00000000000000describe('$httpBackend', function() { var $backend, $browser, callbacks, xhr, fakeDocument, callback, fakeTimeoutId = 0; // TODO(vojta): should be replaced by $defer mock function fakeTimeout(fn, delay) { fakeTimeout.fns.push(fn); fakeTimeout.delays.push(delay); fakeTimeout.ids.push(++fakeTimeoutId); return fakeTimeoutId; } fakeTimeout.fns = []; fakeTimeout.delays = []; fakeTimeout.ids = []; fakeTimeout.flush = function() { var len = fakeTimeout.fns.length; fakeTimeout.delays = []; fakeTimeout.ids = []; while (len--) fakeTimeout.fns.shift()(); }; fakeTimeout.cancel = function(id) { var i = indexOf(fakeTimeout.ids, id); if (i >= 0) { fakeTimeout.fns.splice(i, 1); fakeTimeout.delays.splice(i, 1); fakeTimeout.ids.splice(i, 1); return true; } return false; }; beforeEach(inject(function($injector) { callbacks = {counter: 0}; $browser = $injector.get('$browser'); fakeDocument = { $$scripts: [], createElement: jasmine.createSpy('createElement').andCallFake(function() { return {}; }), body: { appendChild: jasmine.createSpy('body.appendChild').andCallFake(function(script) { fakeDocument.$$scripts.push(script); }), removeChild: jasmine.createSpy('body.removeChild').andCallFake(function(script) { var index = indexOf(fakeDocument.$$scripts, script); if (index != -1) { fakeDocument.$$scripts.splice(index, 1); } }) } }; $backend = createHttpBackend($browser, createMockXhr, fakeTimeout, callbacks, fakeDocument); callback = jasmine.createSpy('done'); })); it('should do basics - open async xhr and send data', function() { $backend('GET', '/some-url', 'some-data', noop); xhr = MockXhr.$$lastInstance; expect(xhr.$$method).toBe('GET'); expect(xhr.$$url).toBe('/some-url'); expect(xhr.$$data).toBe('some-data'); expect(xhr.$$async).toBe(true); }); it('should pass null to send if no body is set', function() { $backend('GET', '/some-url', null, noop); xhr = MockXhr.$$lastInstance; expect(xhr.$$data).toBe(null); }); it('should normalize IE\'s 1223 status code into 204', function() { callback.andCallFake(function(status) { expect(status).toBe(204); }); $backend('GET', 'URL', null, callback); xhr = MockXhr.$$lastInstance; xhr.status = 1223; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); // onreadystatechange might by called multiple times // with readyState === 4 on mobile webkit caused by // xhrs that are resolved while the app is in the background (see #5426). it('should not process onreadystatechange callback with readyState == 4 more than once', function() { $backend('GET', 'URL', null, callback); xhr = MockXhr.$$lastInstance; xhr.status = 200; xhr.readyState = 4; xhr.onreadystatechange(); xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); it('should set only the requested headers', function() { $backend('POST', 'URL', null, noop, {'X-header1': 'value1', 'X-header2': 'value2'}); xhr = MockXhr.$$lastInstance; expect(xhr.$$reqHeaders).toEqual({ 'X-header1': 'value1', 'X-header2': 'value2' }); }); it('should set requested headers even if they have falsy values', function() { $backend('POST', 'URL', null, noop, { 'X-header1': 0, 'X-header2': '', 'X-header3': false, 'X-header4': undefined }); xhr = MockXhr.$$lastInstance; expect(xhr.$$reqHeaders).toEqual({ 'X-header1': 0, 'X-header2': '', 'X-header3': false }); }); it('should not try to read response data when request is aborted', function() { callback.andCallFake(function(status, response, headers) { expect(status).toBe(-1); expect(response).toBe(null); expect(headers).toBe(null); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); fakeTimeout.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); it('should abort request on timeout', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); expect(fakeTimeout.delays[0]).toBe(2000); fakeTimeout.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); it('should abort request on timeout promise resolution', inject(function($timeout) { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('GET', '/url', null, callback, {}, $timeout(noop, 2000)); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); $timeout.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); })); it('should not abort resolved request on timeout promise resolution', inject(function($timeout) { callback.andCallFake(function(status, response) { expect(status).toBe(200); }); $backend('GET', '/url', null, callback, {}, $timeout(noop, 2000)); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); xhr.status = 200; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); $timeout.flush(); expect(xhr.abort).not.toHaveBeenCalled(); })); it('should cancel timeout on completion', function() { callback.andCallFake(function(status, response) { expect(status).toBe(200); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); expect(fakeTimeout.delays[0]).toBe(2000); xhr.status = 200; xhr.readyState = 4; xhr.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); expect(fakeTimeout.delays.length).toBe(0); expect(xhr.abort).not.toHaveBeenCalled(); }); it('should register onreadystatechange callback before sending', function() { // send() in IE6, IE7 is sync when serving from cache function SyncXhr() { xhr = this; this.open = this.setRequestHeader = noop; this.send = function() { this.status = 200; this.responseText = 'response'; this.readyState = 4; this.onreadystatechange(); }; this.getAllResponseHeaders = valueFn(''); } callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('response'); }); $backend = createHttpBackend($browser, function() { return new SyncXhr() }); $backend('GET', '/url', null, callback); expect(callback).toHaveBeenCalledOnce(); }); it('should set withCredentials', function() { $backend('GET', '/some.url', null, callback, {}, null, true); expect(MockXhr.$$lastInstance.withCredentials).toBe(true); }); describe('responseType', function() { it('should set responseType and return xhr.response', function() { $backend('GET', '/whatever', null, callback, {}, null, null, 'blob'); var xhrInstance = MockXhr.$$lastInstance; expect(xhrInstance.responseType).toBe('blob'); callback.andCallFake(function(status, response) { expect(response).toBe(xhrInstance.response); }); xhrInstance.response = {some: 'object'}; xhrInstance.readyState = 4; xhrInstance.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); it('should read responseText if response was not defined', function() { // old browsers like IE8, don't support responseType, so they always respond with responseText $backend('GET', '/whatever', null, callback, {}, null, null, 'blob'); var xhrInstance = MockXhr.$$lastInstance; var responseText = '{"some": "object"}'; expect(xhrInstance.responseType).toBe('blob'); callback.andCallFake(function(status, response) { expect(response).toBe(responseText); }); xhrInstance.responseText = responseText; xhrInstance.readyState = 4; xhrInstance.onreadystatechange(); expect(callback).toHaveBeenCalledOnce(); }); }); describe('JSONP', function() { var SCRIPT_URL = /([^\?]*)\?cb=angular\.callbacks\.(.*)/; it('should add script tag for JSONP request', function() { callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('some-data'); }); $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback); expect(fakeDocument.$$scripts.length).toBe(1); var script = fakeDocument.$$scripts.shift(), url = script.src.match(SCRIPT_URL); expect(url[1]).toBe('http://example.org/path'); callbacks[url[2]]('some-data'); if (script.onreadystatechange) { script.readyState = 'complete'; script.onreadystatechange(); } else { script.onload(); } expect(callback).toHaveBeenCalledOnce(); }); it('should clean up the callback and remove the script', function() { $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback); expect(fakeDocument.$$scripts.length).toBe(1); var script = fakeDocument.$$scripts.shift(), callbackId = script.src.match(SCRIPT_URL)[2]; callbacks[callbackId]('some-data'); if (script.onreadystatechange) { script.readyState = 'complete'; script.onreadystatechange(); } else { script.onload(); } expect(callbacks[callbackId]).toBe(angular.noop); expect(fakeDocument.body.removeChild).toHaveBeenCalledOnceWith(script); }); if(msie<=8) { it('should attach onreadystatechange handler to the script object', function() { $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop); expect(fakeDocument.$$scripts[0].onreadystatechange).toEqual(jasmine.any(Function)); var script = fakeDocument.$$scripts[0]; script.readyState = 'complete'; script.onreadystatechange(); expect(script.onreadystatechange).toBe(null); }); } else { it('should attach onload and onerror handlers to the script object', function() { $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop); expect(fakeDocument.$$scripts[0].onload).toEqual(jasmine.any(Function)); expect(fakeDocument.$$scripts[0].onerror).toEqual(jasmine.any(Function)); var script = fakeDocument.$$scripts[0]; script.onload(); expect(script.onload).toBe(null); expect(script.onerror).toBe(null); }); } it('should call callback with status -2 when script fails to load', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-2); expect(response).toBeUndefined(); }); $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback); expect(fakeDocument.$$scripts.length).toBe(1); var script = fakeDocument.$$scripts.shift(); if (script.onreadystatechange) { script.readyState = 'complete'; script.onreadystatechange(); } else { script.onload(); } expect(callback).toHaveBeenCalledOnce(); }); it('should set url to current location if not specified or empty string', function() { $backend('JSONP', undefined, null, callback); expect(fakeDocument.$$scripts[0].src).toBe($browser.url()); fakeDocument.$$scripts.shift(); $backend('JSONP', '', null, callback); expect(fakeDocument.$$scripts[0].src).toBe($browser.url()); }); it('should abort request on timeout and replace callback with noop', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback, null, 2000); expect(fakeDocument.$$scripts.length).toBe(1); expect(fakeTimeout.delays[0]).toBe(2000); var script = fakeDocument.$$scripts.shift(), callbackId = script.src.match(SCRIPT_URL)[2]; fakeTimeout.flush(); expect(fakeDocument.$$scripts.length).toBe(0); expect(callback).toHaveBeenCalledOnce(); expect(callbacks[callbackId]).toBe(angular.noop); }); // TODO(vojta): test whether it fires "async-start" // TODO(vojta): test whether it fires "async-end" on both success and error }); describe('protocols that return 0 status code', function() { function respond(status, content) { xhr = MockXhr.$$lastInstance; xhr.status = status; xhr.responseText = content; xhr.readyState = 4; xhr.onreadystatechange(); } it('should convert 0 to 200 if content', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'someProtocol:///whatever/index.html', null, callback); respond(0, 'SOME CONTENT'); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(200); }); it('should convert 0 to 404 if no content', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'someProtocol:///whatever/index.html', null, callback); respond(0, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(404); }); it('should convert 0 to 404 if no content - relative url', function() { var originalUrlParsingNode = urlParsingNode; //temporarily overriding the DOM element to pretend that the test runs origin with file:// protocol urlParsingNode = { hash : "#/C:/", host : "", hostname : "", href : "someProtocol:///C:/base#!/C:/foo", pathname : "/C:/foo", port : "", protocol : "someProtocol:", search : "", setAttribute: angular.noop }; try { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', '/whatever/index.html', null, callback); respond(0, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(404); } finally { urlParsingNode = originalUrlParsingNode; } }); it('should return original backend status code if different from 0', function () { $backend = createHttpBackend($browser, createMockXhr); // request to http:// $backend('POST', 'http://rest_api/create_whatever', null, callback); respond(201, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(201); // request to file:// $backend('POST', 'file://rest_api/create_whatever', null, callback); respond(201, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(201); // request to file:// with HTTP status >= 300 $backend('POST', 'file://rest_api/create_whatever', null, callback); respond(503, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(503); }); }); }); angular.js-1.2.11/test/ng/httpSpec.js000066400000000000000000001462261227375216300173760ustar00rootroot00000000000000'use strict'; describe('$http', function() { var callback; beforeEach(function() { callback = jasmine.createSpy('done'); }); beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); afterEach(inject(function($exceptionHandler, $httpBackend, $rootScope) { forEach($exceptionHandler.errors, function(e) { dump('Unhandled exception: ', e) }); if ($exceptionHandler.errors.length) { throw 'Unhandled exceptions trapped in $exceptionHandler!'; } $rootScope.$digest(); $httpBackend.verifyNoOutstandingExpectation(); })); describe('$httpProvider', function() { describe('interceptors', function() { it('should accept injected rejected response interceptor', function() { var wasCalled = false; module(function($httpProvider, $provide) { $httpProvider.responseInterceptors.push('injectedInterceptor'); $provide.factory('injectedInterceptor', ['$q', function($q) { return function(promise) { return promise.then(null, function authInterceptor(response) { wasCalled = true; expect(response.status).toEqual(401); return $q.reject(response); }); }; }]); }); inject(function($http, $httpBackend) { $httpBackend.expect('GET', '/url').respond(401); $http({method: 'GET', url: '/url'}); $httpBackend.flush(); expect(wasCalled).toEqual(true); }); }); it('should chain request, requestReject, response and responseReject interceptors', function() { module(function($httpProvider) { var savedConfig, savedResponse; $httpProvider.interceptors.push(function($q) { return { request: function(config) { config.url += '/1'; savedConfig = config; return $q.reject('/2'); } }; }); $httpProvider.interceptors.push(function($q) { return { requestError: function(error) { savedConfig.url += error; return $q.when(savedConfig); } }; }); $httpProvider.interceptors.push(function() { return { responseError: function(rejection) { savedResponse.data += rejection; return savedResponse; } }; }); $httpProvider.interceptors.push(function($q) { return { response: function(response) { response.data += ':1'; savedResponse = response return $q.reject(':2'); } }; }); }); inject(function($http, $httpBackend, $rootScope) { var response; $httpBackend.expect('GET', '/url/1/2').respond('response'); $http({method: 'GET', url: '/url'}).then(function(r) { response = r; }); $rootScope.$apply(); $httpBackend.flush(); expect(response.data).toEqual('response:1:2'); }); }); it('should verify order of execution', function() { module(function($httpProvider) { $httpProvider.interceptors.push(function($q) { return { request: function(config) { config.url += '/outer'; return config; }, response: function(response) { response.data = '{' + response.data + '} outer'; return response; } }; }); $httpProvider.interceptors.push(function($q) { return { request: function(config) { config.url += '/inner'; return config; }, response: function(response) { response.data = '{' + response.data + '} inner'; return response; } }; }); $httpProvider.responseInterceptors.push(function($q) { return function(promise) { var defer = $q.defer(); promise.then(function(response) { response.data = '[' + response.data + '] legacy-1'; defer.resolve(response); }); return defer.promise; }; }); $httpProvider.responseInterceptors.push(function($q) { return function(promise) { var defer = $q.defer(); promise.then(function(response) { response.data = '[' + response.data + '] legacy-2'; defer.resolve(response); }); return defer.promise; }; }); }); inject(function($http, $httpBackend) { var response; $httpBackend.expect('GET', '/url/outer/inner').respond('response'); $http({method: 'GET', url: '/url'}).then(function(r) { response = r; }); $httpBackend.flush(); expect(response.data).toEqual('{{[[response] legacy-1] legacy-2} inner} outer'); }); }); }); describe('response interceptors', function() { it('should default to an empty array', module(function($httpProvider) { expect($httpProvider.responseInterceptors).toEqual([]); })); it('should pass the responses through interceptors', function() { module(function($httpProvider, $provide) { $provide.factory('testInterceptor', function ($q) { return function(httpPromise) { return httpPromise.then(function(response) { var deferred = $q.defer(); deferred.resolve({ data: response.data + '?', status: 209, headers: response.headers, request: response.config }); return deferred.promise; }); }; }); // just change the response data and pass the response object along $httpProvider.responseInterceptors.push(function() { return function(httpPromise) { return httpPromise.then(function(response) { response.data += '!'; return response; }); } }); // return a new resolved promise representing modified response object $httpProvider.responseInterceptors.push('testInterceptor'); }); inject(function($http, $httpBackend) { $httpBackend.expect('GET', '/foo').respond(201, 'Hello'); $http.get('/foo').success(function(data, status) { expect(data).toBe('Hello!?'); expect(status).toBe(209); callback(); }); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); }); it('should support interceptors defined as services', function() { module(function($provide, $httpProvider) { $provide.factory('myInterceptor', function() { return function(promise) { return promise.then(function(response) { response.data = uppercase(response.data); return response; }); } }); $httpProvider.responseInterceptors.push('myInterceptor'); }); inject(function($http, $httpBackend) { var response; $httpBackend.expect('GET', '/test').respond('hello!'); $http.get('/test').success(function(data) {response = data;}); expect(response).toBeUndefined(); $httpBackend.flush(); expect(response).toBe('HELLO!'); }); }); }); describe('request interceptors', function() { it('should pass request config as a promise', function() { var run = false; module(function($httpProvider) { $httpProvider.interceptors.push(function() { return { request: function(config) { expect(config.url).toEqual('/url'); expect(config.data).toEqual({one: "two"}); expect(config.headers.foo).toEqual('bar'); run = true; return config; } }; }); }); inject(function($http, $httpBackend, $rootScope) { $httpBackend.expect('POST', '/url').respond(''); $http({method: 'POST', url: '/url', data: {one: 'two'}, headers: {foo: 'bar'}}); $rootScope.$apply(); expect(run).toEqual(true); }); }); it('should allow manipulation of request', function() { module(function($httpProvider) { $httpProvider.interceptors.push(function() { return { request: function(config) { config.url = '/intercepted'; config.headers.foo = 'intercepted'; return config; } }; }); }); inject(function($http, $httpBackend, $rootScope) { $httpBackend.expect('GET', '/intercepted', null, function (headers) { return headers.foo === 'intercepted'; }).respond(''); $http.get('/url'); $rootScope.$apply(); }); }); it('should allow replacement of the headers object', function() { module(function($httpProvider) { $httpProvider.interceptors.push(function() { return { request: function(config) { config.headers = {foo: 'intercepted'}; return config; } }; }); }); inject(function($http, $httpBackend, $rootScope) { $httpBackend.expect('GET', '/url', null, function (headers) { return angular.equals(headers, {foo: 'intercepted'}); }).respond(''); $http.get('/url'); $rootScope.$apply(); }); }); it('should reject the http promise if an interceptor fails', function() { var reason = new Error('interceptor failed'); module(function($httpProvider) { $httpProvider.interceptors.push(function($q) { return { request: function(promise) { return $q.reject(reason); } }; }); }); inject(function($http, $httpBackend, $rootScope) { var success = jasmine.createSpy(), error = jasmine.createSpy(); $http.get('/url').then(success, error); $rootScope.$apply(); expect(success).not.toHaveBeenCalled(); expect(error).toHaveBeenCalledWith(reason); }); }); it('should not manipulate the passed-in config', function() { module(function($httpProvider) { $httpProvider.interceptors.push(function() { return { request: function(config) { config.url = '/intercepted'; config.headers.foo = 'intercepted'; return config; } }; }); }); inject(function($http, $httpBackend, $rootScope) { var config = { method: 'get', url: '/url', headers: { foo: 'bar'} }; $httpBackend.expect('GET', '/intercepted').respond(''); $http.get('/url'); $rootScope.$apply(); expect(config.method).toEqual('get'); expect(config.url).toEqual('/url'); expect(config.headers.foo).toEqual('bar') }); }); it('should support interceptors defined as services', function() { module(function($provide, $httpProvider) { $provide.factory('myInterceptor', function() { return { request: function(config) { config.url = '/intercepted'; return config; } }; }); $httpProvider.interceptors.push('myInterceptor'); }); inject(function($http, $httpBackend, $rootScope) { $httpBackend.expect('POST', '/intercepted').respond(''); $http.post('/url'); $rootScope.$apply(); }); }); it('should support complex interceptors based on promises', function() { module(function($provide, $httpProvider) { $provide.factory('myInterceptor', function($q, $rootScope) { return { request: function(config) { return $q.when('/intercepted').then(function(intercepted) { config.url = intercepted; return config; }); } }; }); $httpProvider.interceptors.push('myInterceptor'); }); inject(function($http, $httpBackend, $rootScope) { $httpBackend.expect('POST', '/intercepted').respond(''); $http.post('/two'); $rootScope.$apply(); }); }); }); }); describe('the instance', function() { var $httpBackend, $http, $rootScope; beforeEach(inject(['$rootScope', function($rs) { $rootScope = $rs; spyOn($rootScope, '$apply').andCallThrough(); }])); beforeEach(inject(['$httpBackend', '$http', function($hb, $h) { $httpBackend = $hb; $http = $h; }])); it('should do basic request', inject(function($httpBackend, $http) { $httpBackend.expect('GET', '/url').respond(''); $http({url: '/url', method: 'GET'}); })); it('should pass data if specified', inject(function($httpBackend, $http) { $httpBackend.expect('POST', '/url', 'some-data').respond(''); $http({url: '/url', method: 'POST', data: 'some-data'}); })); describe('params', function() { it('should do basic request with params and encode', inject(function($httpBackend, $http) { $httpBackend.expect('GET', '/url?a%3D=%3F%26&b=2').respond(''); $http({url: '/url', params: {'a=':'?&', b:2}, method: 'GET'}); })); it('should merge params if url contains some already', inject(function($httpBackend, $http) { $httpBackend.expect('GET', '/url?c=3&a=1&b=2').respond(''); $http({url: '/url?c=3', params: {a:1, b:2}, method: 'GET'}); })); it('should jsonify objects in params map', inject(function($httpBackend, $http) { $httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond(''); $http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'}); })); it('should expand arrays in params map', inject(function($httpBackend, $http) { $httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond(''); $http({url: '/url', params: {a: [1,2,3]}, method: 'GET'}); })); it('should not encode @ in url params', function() { //encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt //with regards to the character set (pchar) allowed in path segments //so we need this test to make sure that we don't over-encode the params and break stuff //like buzz api which uses @self $httpBackend.expect('GET', '/Path?!do%26h=g%3Da+h&:bar=$baz@1').respond(''); $http({url: '/Path', params: {':bar': '$baz@1', '!do&h': 'g=a h'}, method: 'GET'}); }); }); describe('callbacks', function() { it('should pass in the response object when a request is successful', function() { $httpBackend.expect('GET', '/url').respond(207, 'my content', {'content-encoding': 'smurf'}); $http({url: '/url', method: 'GET'}).then(function(response) { expect(response.data).toBe('my content'); expect(response.status).toBe(207); expect(response.headers()).toEqual({'content-encoding': 'smurf'}); expect(response.config.url).toBe('/url'); callback(); }); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should pass in the response object when a request failed', function() { $httpBackend.expect('GET', '/url').respond(543, 'bad error', {'request-id': '123'}); $http({url: '/url', method: 'GET'}).then(null, function(response) { expect(response.data).toBe('bad error'); expect(response.status).toBe(543); expect(response.headers()).toEqual({'request-id': '123'}); expect(response.config.url).toBe('/url'); callback(); }); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); describe('success', function() { it('should allow http specific callbacks to be registered via "success"', function() { $httpBackend.expect('GET', '/url').respond(207, 'my content', {'content-encoding': 'smurf'}); $http({url: '/url', method: 'GET'}).success(function(data, status, headers, config) { expect(data).toBe('my content'); expect(status).toBe(207); expect(headers()).toEqual({'content-encoding': 'smurf'}); expect(config.url).toBe('/url'); callback(); }); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return the original http promise', function() { $httpBackend.expect('GET', '/url').respond(207, 'my content', {'content-encoding': 'smurf'}); var httpPromise = $http({url: '/url', method: 'GET'}); expect(httpPromise.success(callback)).toBe(httpPromise); }); }); describe('error', function() { it('should allow http specific callbacks to be registered via "error"', function() { $httpBackend.expect('GET', '/url').respond(543, 'bad error', {'request-id': '123'}); $http({url: '/url', method: 'GET'}).error(function(data, status, headers, config) { expect(data).toBe('bad error'); expect(status).toBe(543); expect(headers()).toEqual({'request-id': '123'}); expect(config.url).toBe('/url'); callback(); }); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return the original http promise', function() { $httpBackend.expect('GET', '/url').respond(543, 'bad error', {'request-id': '123'}); var httpPromise = $http({url: '/url', method: 'GET'}); expect(httpPromise.error(callback)).toBe(httpPromise); }); }); }); describe('response headers', function() { it('should return single header', function() { $httpBackend.expect('GET', '/url').respond('', {'date': 'date-val'}); callback.andCallFake(function(r) { expect(r.headers('date')).toBe('date-val'); }); $http({url: '/url', method: 'GET'}).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return null when single header does not exist', function() { $httpBackend.expect('GET', '/url').respond('', {'Some-Header': 'Fake'}); callback.andCallFake(function(r) { r.headers(); // we need that to get headers parsed first expect(r.headers('nothing')).toBe(null); }); $http({url: '/url', method: 'GET'}).then(callback) $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return all headers as object', function() { $httpBackend.expect('GET', '/url').respond('', { 'content-encoding': 'gzip', 'server': 'Apache' }); callback.andCallFake(function(r) { expect(r.headers()).toEqual({'content-encoding': 'gzip', 'server': 'Apache'}); }); $http({url: '/url', method: 'GET'}).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return empty object for jsonp request', function() { callback.andCallFake(function(r) { expect(r.headers()).toEqual({}); }); $httpBackend.expect('JSONP', '/some').respond(200); $http({url: '/some', method: 'JSONP'}).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); }); describe('response headers parser', function() { it('should parse basic', function() { var parsed = parseHeaders( 'date: Thu, 04 Aug 2011 20:23:08 GMT\n' + 'content-encoding: gzip\n' + 'transfer-encoding: chunked\n' + 'x-cache-info: not cacheable; response has already expired, not cacheable; response has already expired\n' + 'connection: Keep-Alive\n' + 'x-backend-server: pm-dekiwiki03\n' + 'pragma: no-cache\n' + 'server: Apache\n' + 'x-frame-options: DENY\n' + 'content-type: text/html; charset=utf-8\n' + 'vary: Cookie, Accept-Encoding\n' + 'keep-alive: timeout=5, max=1000\n' + 'expires: Thu: , 19 Nov 1981 08:52:00 GMT\n'); expect(parsed['date']).toBe('Thu, 04 Aug 2011 20:23:08 GMT'); expect(parsed['content-encoding']).toBe('gzip'); expect(parsed['transfer-encoding']).toBe('chunked'); expect(parsed['keep-alive']).toBe('timeout=5, max=1000'); }); it('should parse lines without space after colon', function() { expect(parseHeaders('key:value').key).toBe('value'); }); it('should trim the values', function() { expect(parseHeaders('key: value ').key).toBe('value'); }); it('should allow headers without value', function() { expect(parseHeaders('key:').key).toBe(''); }); it('should merge headers with same key', function() { expect(parseHeaders('key: a\nkey:b\n').key).toBe('a, b'); }); it('should normalize keys to lower case', function() { expect(parseHeaders('KeY: value').key).toBe('value'); }); it('should parse CRLF as delimiter', function() { // IE does use CRLF expect(parseHeaders('a: b\r\nc: d\r\n')).toEqual({a: 'b', c: 'd'}); expect(parseHeaders('a: b\r\nc: d\r\n').a).toBe('b'); }); it('should parse tab after semi-colon', function() { expect(parseHeaders('a:\tbb').a).toBe('bb'); expect(parseHeaders('a: \tbb').a).toBe('bb'); }); }); describe('request headers', function() { it('should send custom headers', function() { $httpBackend.expect('GET', '/url', undefined, function(headers) { return headers['Custom'] == 'header'; }).respond(''); $http({url: '/url', method: 'GET', headers: { 'Custom': 'header', }}); $httpBackend.flush(); }); it('should set default headers for GET request', function() { $httpBackend.expect('GET', '/url', undefined, function(headers) { return headers['Accept'] == 'application/json, text/plain, */*'; }).respond(''); $http({url: '/url', method: 'GET', headers: {}}); $httpBackend.flush(); }); it('should set default headers for POST request', function() { $httpBackend.expect('POST', '/url', 'messageBody', function(headers) { return headers['Accept'] == 'application/json, text/plain, */*' && headers['Content-Type'] == 'application/json;charset=utf-8'; }).respond(''); $http({url: '/url', method: 'POST', headers: {}, data: 'messageBody'}); $httpBackend.flush(); }); it('should set default headers for PUT request', function() { $httpBackend.expect('PUT', '/url', 'messageBody', function(headers) { return headers['Accept'] == 'application/json, text/plain, */*' && headers['Content-Type'] == 'application/json;charset=utf-8'; }).respond(''); $http({url: '/url', method: 'PUT', headers: {}, data: 'messageBody'}); $httpBackend.flush(); }); it('should set default headers for PATCH request', function() { $httpBackend.expect('PATCH', '/url', 'messageBody', function(headers) { return headers['Accept'] == 'application/json, text/plain, */*' && headers['Content-Type'] == 'application/json;charset=utf-8'; }).respond(''); $http({url: '/url', method: 'PATCH', headers: {}, data: 'messageBody'}); $httpBackend.flush(); }); it('should set default headers for custom HTTP method', function() { $httpBackend.expect('FOO', '/url', undefined, function(headers) { return headers['Accept'] == 'application/json, text/plain, */*'; }).respond(''); $http({url: '/url', method: 'FOO', headers: {}}); $httpBackend.flush(); }); it('should override default headers with custom', function() { $httpBackend.expect('POST', '/url', 'messageBody', function(headers) { return headers['Accept'] == 'Rewritten' && headers['Content-Type'] == 'Rewritten'; }).respond(''); $http({url: '/url', method: 'POST', data: 'messageBody', headers: { 'Accept': 'Rewritten', 'Content-Type': 'Rewritten' }}); $httpBackend.flush(); }); it('should override default headers with custom in a case insensitive manner', function() { $httpBackend.expect('POST', '/url', 'messageBody', function(headers) { return headers['accept'] == 'Rewritten' && headers['content-type'] == 'Content-Type Rewritten' && headers['Accept'] === undefined && headers['Content-Type'] === undefined; }).respond(''); $http({url: '/url', method: 'POST', data: 'messageBody', headers: { 'accept': 'Rewritten', 'content-type': 'Content-Type Rewritten' }}); $httpBackend.flush(); }); it('should not set XSRF cookie for cross-domain requests', inject(function($browser) { $browser.cookies('XSRF-TOKEN', 'secret'); $browser.url('http://host.com/base'); $httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) { return headers['X-XSRF-TOKEN'] === undefined; }).respond(''); $http({url: 'http://www.test.com/url', method: 'GET', headers: {}}); $httpBackend.flush(); })); it('should not send Content-Type header if request data/body is undefined', function() { $httpBackend.expect('POST', '/url', undefined, function(headers) { return !headers.hasOwnProperty('Content-Type'); }).respond(''); $httpBackend.expect('POST', '/url2', undefined, function(headers) { return !headers.hasOwnProperty('content-type'); }).respond(''); $http({url: '/url', method: 'POST'}); $http({url: '/url2', method: 'POST', headers: {'content-type': 'Rewritten'}}); $httpBackend.flush(); }); it('should set the XSRF cookie into a XSRF header', inject(function($browser) { function checkXSRF(secret, header) { return function(headers) { return headers[header || 'X-XSRF-TOKEN'] == secret; }; } $browser.cookies('XSRF-TOKEN', 'secret'); $browser.cookies('aCookie', 'secret2'); $httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('GET', '/url', undefined, checkXSRF('secret', 'aHeader')).respond(''); $httpBackend.expect('GET', '/url', undefined, checkXSRF('secret2')).respond(''); $http({url: '/url', method: 'GET'}); $http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}}); $http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}}); $http({url: '/url', method: 'DELETE', headers: {}}); $http({url: '/url', method: 'GET', xsrfHeaderName: 'aHeader'}) $http({url: '/url', method: 'GET', xsrfCookieName: 'aCookie'}) $httpBackend.flush(); })); it('should send execute result if header value is function', inject(function() { var headerConfig = {'Accept': function() { return 'Rewritten'; }}; function checkHeaders(headers) { return headers['Accept'] == 'Rewritten'; } $httpBackend.expect('GET', '/url', undefined, checkHeaders).respond(''); $httpBackend.expect('POST', '/url', undefined, checkHeaders).respond(''); $httpBackend.expect('PUT', '/url', undefined, checkHeaders).respond(''); $httpBackend.expect('PATCH', '/url', undefined, checkHeaders).respond(''); $httpBackend.expect('DELETE', '/url', undefined, checkHeaders).respond(''); $http({url: '/url', method: 'GET', headers: headerConfig}); $http({url: '/url', method: 'POST', headers: headerConfig}); $http({url: '/url', method: 'PUT', headers: headerConfig}); $http({url: '/url', method: 'PATCH', headers: headerConfig}); $http({url: '/url', method: 'DELETE', headers: headerConfig}); $httpBackend.flush(); })); }); describe('short methods', function() { function checkHeader(name, value) { return function(headers) { return headers[name] == value; }; } it('should have get()', function() { $httpBackend.expect('GET', '/url').respond(''); $http.get('/url'); }); it('get() should allow config param', function() { $httpBackend.expect('GET', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.get('/url', {headers: {'Custom': 'Header'}}); }); it('should have delete()', function() { $httpBackend.expect('DELETE', '/url').respond(''); $http['delete']('/url'); }); it('delete() should allow config param', function() { $httpBackend.expect('DELETE', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http['delete']('/url', {headers: {'Custom': 'Header'}}); }); it('should have head()', function() { $httpBackend.expect('HEAD', '/url').respond(''); $http.head('/url'); }); it('head() should allow config param', function() { $httpBackend.expect('HEAD', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.head('/url', {headers: {'Custom': 'Header'}}); }); it('should have post()', function() { $httpBackend.expect('POST', '/url', 'some-data').respond(''); $http.post('/url', 'some-data'); }); it('post() should allow config param', function() { $httpBackend.expect('POST', '/url', 'some-data', checkHeader('Custom', 'Header')).respond(''); $http.post('/url', 'some-data', {headers: {'Custom': 'Header'}}); }); it('should have put()', function() { $httpBackend.expect('PUT', '/url', 'some-data').respond(''); $http.put('/url', 'some-data'); }); it('put() should allow config param', function() { $httpBackend.expect('PUT', '/url', 'some-data', checkHeader('Custom', 'Header')).respond(''); $http.put('/url', 'some-data', {headers: {'Custom': 'Header'}}); }); it('should have jsonp()', function() { $httpBackend.expect('JSONP', '/url').respond(''); $http.jsonp('/url'); }); it('jsonp() should allow config param', function() { $httpBackend.expect('JSONP', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.jsonp('/url', {headers: {'Custom': 'Header'}}); }); }); describe('scope.$apply', function() { it('should $apply after success callback', function() { $httpBackend.when('GET').respond(200); $http({method: 'GET', url: '/some'}); $httpBackend.flush(); expect($rootScope.$apply).toHaveBeenCalledOnce(); }); it('should $apply after error callback', function() { $httpBackend.when('GET').respond(404); $http({method: 'GET', url: '/some'}); $httpBackend.flush(); expect($rootScope.$apply).toHaveBeenCalledOnce(); }); it('should $apply even if exception thrown during callback', inject(function($exceptionHandler){ $httpBackend.when('GET').respond(200); callback.andThrow('error in callback'); $http({method: 'GET', url: '/some'}).then(callback); $httpBackend.flush(); expect($rootScope.$apply).toHaveBeenCalledOnce(); $exceptionHandler.errors = []; })); }); describe('transformData', function() { describe('request', function() { describe('default', function() { it('should transform object into json', function() { $httpBackend.expect('POST', '/url', '{"one":"two"}').respond(''); $http({method: 'POST', url: '/url', data: {one: 'two'}}); }); it('should ignore strings', function() { $httpBackend.expect('POST', '/url', 'string-data').respond(''); $http({method: 'POST', url: '/url', data: 'string-data'}); }); it('should ignore File objects', function() { var file = { some: true, // $httpBackend compares toJson values by default, // we need to be sure it's not serialized into json string test: function(actualValue) { return this === actualValue; } }; // I'm really sorry for doing this :-D // Unfortunatelly I don't know how to trick toString.apply(obj) comparison spyOn(window, 'isFile').andReturn(true); $httpBackend.expect('POST', '/some', file).respond(''); $http({method: 'POST', url: '/some', data: file}); }); }); it('should have access to request headers', function() { $httpBackend.expect('POST', '/url', 'header1').respond(200); $http.post('/url', 'req', { headers: {h1: 'header1'}, transformRequest: function(data, headers) { return headers('h1'); } }).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should pipeline more functions', function() { function first(d, h) {return d + '-first' + ':' + h('h1')} function second(d) {return uppercase(d)} $httpBackend.expect('POST', '/url', 'REQ-FIRST:V1').respond(200); $http.post('/url', 'req', { headers: {h1: 'v1'}, transformRequest: [first, second] }).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); }); describe('response', function() { describe('default', function() { it('should deserialize json objects', function() { $httpBackend.expect('GET', '/url').respond('{"foo":"bar","baz":23}'); $http({method: 'GET', url: '/url'}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual({foo: 'bar', baz: 23}); }); it('should deserialize json arrays', function() { $httpBackend.expect('GET', '/url').respond('[1, "abc", {"foo":"bar"}]'); $http({method: 'GET', url: '/url'}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual([1, 'abc', {foo: 'bar'}]); }); it('should deserialize json with security prefix', function() { $httpBackend.expect('GET', '/url').respond(')]}\',\n[1, "abc", {"foo":"bar"}]'); $http({method: 'GET', url: '/url'}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual([1, 'abc', {foo:'bar'}]); }); it('should deserialize json with security prefix ")]}\'"', function() { $httpBackend.expect('GET', '/url').respond(')]}\'\n\n[1, "abc", {"foo":"bar"}]'); $http({method: 'GET', url: '/url'}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual([1, 'abc', {foo:'bar'}]); }); it('should not deserialize tpl beginning with ng expression', function() { $httpBackend.expect('GET', '/url').respond('{{some}}'); $http.get('/url').success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual('{{some}}'); }); }); it('should have access to response headers', function() { $httpBackend.expect('GET', '/url').respond(200, 'response', {h1: 'header1'}); $http.get('/url', { transformResponse: function(data, headers) { return headers('h1'); } }).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('header1'); }); it('should pipeline more functions', function() { function first(d, h) {return d + '-first' + ':' + h('h1')} function second(d) {return uppercase(d)} $httpBackend.expect('POST', '/url').respond(200, 'resp', {h1: 'v1'}); $http.post('/url', '', {transformResponse: [first, second]}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('RESP-FIRST:V1'); }); }); }); describe('cache', function() { var cache; beforeEach(inject(function($cacheFactory) { cache = $cacheFactory('testCache'); })); function doFirstCacheRequest(method, respStatus, headers) { $httpBackend.expect(method || 'GET', '/url').respond(respStatus || 200, 'content', headers); $http({method: method || 'GET', url: '/url', cache: cache}); $httpBackend.flush(); } it('should cache GET request when cache is provided', inject(function($rootScope) { doFirstCacheRequest(); $http({method: 'get', url: '/url', cache: cache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content'); })); it('should not cache when cache is not provided', function() { doFirstCacheRequest(); $httpBackend.expect('GET', '/url').respond(); $http({method: 'GET', url: '/url'}); }); it('should perform request when cache cleared', function() { doFirstCacheRequest(); cache.removeAll(); $httpBackend.expect('GET', '/url').respond(); $http({method: 'GET', url: '/url', cache: cache}); }); it('should always call callback asynchronously', function() { doFirstCacheRequest(); $http({method: 'get', url: '/url', cache: cache}).then(callback); expect(callback).not.toHaveBeenCalled(); }); it('should not cache POST request', function() { doFirstCacheRequest('POST'); $httpBackend.expect('POST', '/url').respond('content2'); $http({method: 'POST', url: '/url', cache: cache}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should not cache PUT request', function() { doFirstCacheRequest('PUT'); $httpBackend.expect('PUT', '/url').respond('content2'); $http({method: 'PUT', url: '/url', cache: cache}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should not cache DELETE request', function() { doFirstCacheRequest('DELETE'); $httpBackend.expect('DELETE', '/url').respond(206); $http({method: 'DELETE', url: '/url', cache: cache}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should not cache non 2xx responses', function() { doFirstCacheRequest('GET', 404); $httpBackend.expect('GET', '/url').respond('content2'); $http({method: 'GET', url: '/url', cache: cache}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should cache the headers as well', inject(function($rootScope) { doFirstCacheRequest('GET', 200, {'content-encoding': 'gzip', 'server': 'Apache'}); callback.andCallFake(function(r, s, headers) { expect(headers()).toEqual({'content-encoding': 'gzip', 'server': 'Apache'}); expect(headers('server')).toBe('Apache'); }); $http({method: 'GET', url: '/url', cache: cache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); })); it('should not share the cached headers object instance', inject(function($rootScope) { doFirstCacheRequest('GET', 200, {'content-encoding': 'gzip', 'server': 'Apache'}); callback.andCallFake(function(r, s, headers) { expect(headers()).toEqual(cache.get('/url')[2]); expect(headers()).not.toBe(cache.get('/url')[2]); }); $http({method: 'GET', url: '/url', cache: cache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); })); it('should cache status code as well', inject(function($rootScope) { doFirstCacheRequest('GET', 201); callback.andCallFake(function(r, status, h) { expect(status).toBe(201); }); $http({method: 'get', url: '/url', cache: cache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); })); it('should use cache even if second request was made before the first returned', function() { $httpBackend.expect('GET', '/url').respond(201, 'fake-response'); callback.andCallFake(function(response, status, headers) { expect(response).toBe('fake-response'); expect(status).toBe(201); }); $http({method: 'GET', url: '/url', cache: cache}).success(callback); $http({method: 'GET', url: '/url', cache: cache}).success(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalled(); expect(callback.callCount).toBe(2); }); it('should allow the cached value to be an empty string', function () { cache.put('/abc', ''); callback.andCallFake(function (response, status, headers) { expect(response).toBe(''); expect(status).toBe(200); }); $http({method: 'GET', url: '/abc', cache: cache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalled(); }); it('should default to status code 200 and empty headers if cache contains a non-array element', inject(function($rootScope) { cache.put('/myurl', 'simple response'); $http.get('/myurl', {cache: cache}).success(function(data, status, headers) { expect(data).toBe('simple response'); expect(status).toBe(200); expect(headers()).toEqual({}); callback(); }); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); }) ); describe('$http.defaults.cache', function () { it('should be undefined by default', function() { expect($http.defaults.cache).toBeUndefined() }); it('should cache requests when no cache given in request config', function() { $http.defaults.cache = cache; // First request fills the cache from server response. $httpBackend.expect('GET', '/url').respond(200, 'content'); $http({method: 'GET', url: '/url'}); // Notice no cache given in config. $httpBackend.flush(); // Second should be served from cache, without sending request to server. $http({method: 'get', url: '/url'}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content'); // Invalidate cache entry. $http.defaults.cache.remove("/url"); // After cache entry removed, a request should be sent to server. $httpBackend.expect('GET', '/url').respond(200, 'content'); $http({method: 'GET', url: '/url'}); $httpBackend.flush(); }); it('should have less priority than explicitly given cache', inject(function($cacheFactory) { var localCache = $cacheFactory('localCache'); $http.defaults.cache = cache; // Fill local cache. $httpBackend.expect('GET', '/url').respond(200, 'content-local-cache'); $http({method: 'GET', url: '/url', cache: localCache}); $httpBackend.flush(); // Fill default cache. $httpBackend.expect('GET', '/url').respond(200, 'content-default-cache'); $http({method: 'GET', url: '/url'}); $httpBackend.flush(); // Serve request from default cache when no local given. $http({method: 'get', url: '/url'}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content-default-cache'); callback.reset(); // Serve request from local cache when it is given (but default filled too). $http({method: 'get', url: '/url', cache: localCache}).success(callback); $rootScope.$digest(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe('content-local-cache'); })); it('should be skipped if {cache: false} is passed in request config', function() { $http.defaults.cache = cache; $httpBackend.expect('GET', '/url').respond(200, 'content'); $http({method: 'GET', url: '/url'}); $httpBackend.flush(); $httpBackend.expect('GET', '/url').respond(); $http({method: 'GET', url: '/url', cache: false}); $httpBackend.flush(); }); }); }); describe('timeout', function() { it('should abort requests when timeout promise resolves', inject(function($q) { var canceler = $q.defer(); $httpBackend.expect('GET', '/some').respond(200); $http({method: 'GET', url: '/some', timeout: canceler.promise}).error( function(data, status, headers, config) { expect(data).toBeUndefined(); expect(status).toBe(0); expect(headers()).toEqual({}); expect(config.url).toBe('/some'); callback(); }); $rootScope.$apply(function() { canceler.resolve(); }); expect(callback).toHaveBeenCalled(); $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); })); }); describe('pendingRequests', function() { it('should be an array of pending requests', function() { $httpBackend.when('GET').respond(200); expect($http.pendingRequests.length).toBe(0); $http({method: 'get', url: '/some'}); $rootScope.$digest(); expect($http.pendingRequests.length).toBe(1); $httpBackend.flush(); expect($http.pendingRequests.length).toBe(0); }); it('should update pending requests even when served from cache', inject(function($rootScope) { $httpBackend.when('GET').respond(200); $http({method: 'get', url: '/cached', cache: true}); $http({method: 'get', url: '/cached', cache: true}); $rootScope.$digest(); expect($http.pendingRequests.length).toBe(2); $httpBackend.flush(); expect($http.pendingRequests.length).toBe(0); $http({method: 'get', url: '/cached', cache: true}); spyOn($http.pendingRequests, 'push').andCallThrough(); $rootScope.$digest(); expect($http.pendingRequests.push).toHaveBeenCalledOnce(); $rootScope.$apply(); expect($http.pendingRequests.length).toBe(0); })); it('should remove the request before firing callbacks', function() { $httpBackend.when('GET').respond(200); $http({method: 'get', url: '/url'}).success(function() { expect($http.pendingRequests.length).toBe(0); }); $rootScope.$digest(); expect($http.pendingRequests.length).toBe(1); $httpBackend.flush(); }); }); describe('defaults', function() { it('should expose the defaults object at runtime', function() { expect($http.defaults).toBeDefined(); $http.defaults.headers.common.foo = 'bar'; $httpBackend.expect('GET', '/url', undefined, function(headers) { return headers['foo'] == 'bar'; }).respond(''); $http.get('/url'); $httpBackend.flush(); }); it('should have seperate opbjects for defaults PUT and POST', function() { expect($http.defaults.headers.post).not.toBe($http.defaults.headers.put); expect($http.defaults.headers.post).not.toBe($http.defaults.headers.patch); expect($http.defaults.headers.put).not.toBe($http.defaults.headers.patch); }) }); }); it('should pass timeout, withCredentials and responseType', function() { var $httpBackend = jasmine.createSpy('$httpBackend'); $httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) { expect(timeout).toBe(12345); expect(withCredentials).toBe(true); expect(responseType).toBe('json'); }); module(function($provide) { $provide.value('$httpBackend', $httpBackend); }); inject(function($http, $rootScope) { $http({ method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true, responseType: 'json' }); $rootScope.$digest(); expect($httpBackend).toHaveBeenCalledOnce(); }); $httpBackend.verifyNoOutstandingExpectation = noop; }); it('should use withCredentials from default', function() { var $httpBackend = jasmine.createSpy('$httpBackend'); $httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) { expect(withCredentials).toBe(true); }); module(function($provide) { $provide.value('$httpBackend', $httpBackend); }); inject(function($http, $rootScope) { $http.defaults.withCredentials = true; $http({ method: 'GET', url: 'some.html', timeout: 12345, responseType: 'json' }); $rootScope.$digest(); expect($httpBackend).toHaveBeenCalledOnce(); }); $httpBackend.verifyNoOutstandingExpectation = noop; }); }); angular.js-1.2.11/test/ng/interpolateSpec.js000066400000000000000000000243011227375216300207320ustar00rootroot00000000000000'use strict'; describe('$interpolate', function() { it('should return a function when there are no bindings and textOnly is undefined', inject(function($interpolate) { expect(typeof $interpolate('some text')).toBe('function'); })); it('should return undefined when there are no bindings and textOnly is set to true', inject(function($interpolate) { expect($interpolate('some text', true)).toBeUndefined(); })); it('should suppress falsy objects', inject(function($interpolate) { expect($interpolate('{{undefined}}')()).toEqual(''); expect($interpolate('{{undefined+undefined}}')()).toEqual(''); expect($interpolate('{{null}}')()).toEqual(''); expect($interpolate('{{a.b}}')()).toEqual(''); })); it('should jsonify objects', inject(function($interpolate) { expect($interpolate('{{ {} }}')()).toEqual('{}'); expect($interpolate('{{ true }}')()).toEqual('true'); expect($interpolate('{{ false }}')()).toEqual('false'); })); it('should rethrow exceptions', inject(function($interpolate, $rootScope) { $rootScope.err = function () { throw new Error('oops'); }; expect(function () { $interpolate('{{err()}}')($rootScope); }).toThrowMinErr("$interpolate", "interr", "Can't interpolate: {{err()}}\nError: oops"); })); it('should stop interpolation when encountering an exception', inject(function($interpolate, $compile, $rootScope) { $rootScope.err = function () { throw new Error('oops'); }; var dom = jqLite('
                      {{1 + 1}}
                      {{err()}}
                      {{1 + 2}}
                      '); $compile(dom)($rootScope); expect(function () { $rootScope.$apply(); }).toThrowMinErr("$interpolate", "interr", "Can't interpolate: {{err()}}\nError: oops"); expect(dom[0].innerHTML).toEqual('2'); expect(dom[1].innerHTML).toEqual('{{err()}}'); expect(dom[2].innerHTML).toEqual('{{1 + 2}}'); })); it('should return interpolation function', inject(function($interpolate, $rootScope) { $rootScope.name = 'Misko'; expect($interpolate('Hello {{name}}!')($rootScope)).toEqual('Hello Misko!'); })); it('should ignore undefined model', inject(function($interpolate) { expect($interpolate("Hello {{'World' + foo}}")()).toEqual('Hello World'); })); it('should ignore undefined return value', inject(function($interpolate, $rootScope) { $rootScope.foo = function() {return undefined}; expect($interpolate("Hello {{'World' + foo()}}")($rootScope)).toEqual('Hello World'); })); describe('interpolating in a trusted context', function() { var sce; beforeEach(function() { function log() {}; var fakeLog = {log: log, warn: log, info: log, error: log}; module(function($provide, $sceProvider) { $provide.value('$log', fakeLog); $sceProvider.enabled(true); }); inject(['$sce', function($sce) { sce = $sce; }]); }); it('should NOT interpolate non-trusted expressions', inject(function($interpolate) { var foo = "foo"; expect($interpolate('{{foo}}', true, sce.CSS)({}, {foo: foo})).toEqual(''); })); it('should NOT interpolate mistyped expressions', inject(function($interpolate) { var foo = sce.trustAsCss("foo"); expect($interpolate('{{foo}}', true, sce.HTML)({}, {foo: foo})).toEqual(''); })); it('should interpolate trusted expressions in a regular context', inject(function($interpolate) { var foo = sce.trustAsCss("foo"); expect($interpolate('{{foo}}', true)({foo: foo})).toEqual('foo'); })); it('should interpolate trusted expressions in a specific trustedContext', inject(function($interpolate) { var foo = sce.trustAsCss("foo"); expect($interpolate('{{foo}}', true, sce.CSS)({foo: foo})).toEqual('foo'); })); // The concatenation of trusted values does not necessarily result in a trusted value. (For // instance, you can construct evil JS code by putting together pieces of JS strings that are by // themselves safe to execute in isolation.) it('should NOT interpolate trusted expressions with multiple parts', inject(function($interpolate) { var foo = sce.trustAsCss("foo"); var bar = sce.trustAsCss("bar"); expect(function() { return $interpolate('{{foo}}{{bar}}', true, sce.CSS)( {foo: foo, bar: bar}); }).toThrowMinErr( "$interpolate", "noconcat", "Error while interpolating: {{foo}}{{bar}}\n" + "Strict Contextual Escaping disallows interpolations that concatenate multiple " + "expressions when a trusted value is required. See " + "http://docs.angularjs.org/api/ng.$sce"); })); }); describe('provider', function() { beforeEach(module(function($interpolateProvider) { $interpolateProvider.startSymbol('--'); $interpolateProvider.endSymbol('--'); })); it('should not get confused with same markers', inject(function($interpolate) { expect($interpolate('---').parts).toEqual(['---']); expect($interpolate('----')()).toEqual(''); expect($interpolate('--1--')()).toEqual('1'); })); }); describe('parseBindings', function() { it('should Parse Text With No Bindings', inject(function($interpolate) { var parts = $interpolate("a").parts; expect(parts.length).toEqual(1); expect(parts[0]).toEqual("a"); })); it('should Parse Empty Text', inject(function($interpolate) { var parts = $interpolate("").parts; expect(parts.length).toEqual(1); expect(parts[0]).toEqual(""); })); it('should Parse Inner Binding', inject(function($interpolate) { var parts = $interpolate("a{{b}}C").parts; expect(parts.length).toEqual(3); expect(parts[0]).toEqual("a"); expect(parts[1].exp).toEqual("b"); expect(parts[1]({b:123})).toEqual(123); expect(parts[2]).toEqual("C"); })); it('should Parse Ending Binding', inject(function($interpolate) { var parts = $interpolate("a{{b}}").parts; expect(parts.length).toEqual(2); expect(parts[0]).toEqual("a"); expect(parts[1].exp).toEqual("b"); expect(parts[1]({b:123})).toEqual(123); })); it('should Parse Begging Binding', inject(function($interpolate) { var parts = $interpolate("{{b}}c").parts; expect(parts.length).toEqual(2); expect(parts[0].exp).toEqual("b"); expect(parts[1]).toEqual("c"); })); it('should Parse Loan Binding', inject(function($interpolate) { var parts = $interpolate("{{b}}").parts; expect(parts.length).toEqual(1); expect(parts[0].exp).toEqual("b"); })); it('should Parse Two Bindings', inject(function($interpolate) { var parts = $interpolate("{{b}}{{c}}").parts; expect(parts.length).toEqual(2); expect(parts[0].exp).toEqual("b"); expect(parts[1].exp).toEqual("c"); })); it('should Parse Two Bindings With Text In Middle', inject(function($interpolate) { var parts = $interpolate("{{b}}x{{c}}").parts; expect(parts.length).toEqual(3); expect(parts[0].exp).toEqual("b"); expect(parts[1]).toEqual("x"); expect(parts[2].exp).toEqual("c"); })); it('should Parse Multiline', inject(function($interpolate) { var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').parts; expect(parts.length).toEqual(3); expect(parts[0]).toEqual('"X\nY'); expect(parts[1].exp).toEqual('A\n+B'); expect(parts[2]).toEqual('C\nD"'); })); }); describe('isTrustedContext', function() { it('should NOT interpolate a multi-part expression when isTrustedContext is true', inject(function($interpolate) { var isTrustedContext = true; expect(function() { $interpolate('constant/{{var}}', true, isTrustedContext); }).toThrowMinErr( "$interpolate", "noconcat", "Error while interpolating: constant/{{var}}\nStrict " + "Contextual Escaping disallows interpolations that concatenate multiple expressions " + "when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce"); expect(function() { $interpolate('{{foo}}{{bar}}', true, isTrustedContext); }).toThrowMinErr( "$interpolate", "noconcat", "Error while interpolating: {{foo}}{{bar}}\nStrict " + "Contextual Escaping disallows interpolations that concatenate multiple expressions " + "when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce"); })); it('should interpolate a multi-part expression when isTrustedContext is false', inject(function($interpolate) { expect($interpolate('some/{{id}}')()).toEqual('some/'); expect($interpolate('some/{{id}}')({id: 1})).toEqual('some/1'); expect($interpolate('{{foo}}{{bar}}')({foo: 1, bar: 2})).toEqual('12'); })); }); describe('startSymbol', function() { beforeEach(module(function($interpolateProvider) { expect($interpolateProvider.startSymbol()).toBe('{{'); $interpolateProvider.startSymbol('(('); })); it('should expose the startSymbol in config phase', module(function($interpolateProvider) { expect($interpolateProvider.startSymbol()).toBe('(('); })); it('should expose the startSymbol in run phase', inject(function($interpolate) { expect($interpolate.startSymbol()).toBe('(('); })); it('should not get confused by matching start and end symbols', function() { module(function($interpolateProvider) { $interpolateProvider.startSymbol('--'); $interpolateProvider.endSymbol('--'); }); inject(function($interpolate) { expect($interpolate('---').parts).toEqual(['---']); expect($interpolate('----')()).toEqual(''); expect($interpolate('--1--')()).toEqual('1'); }); }); }); describe('endSymbol', function() { beforeEach(module(function($interpolateProvider) { expect($interpolateProvider.endSymbol()).toBe('}}'); $interpolateProvider.endSymbol('))'); })); it('should expose the endSymbol in config phase', module(function($interpolateProvider) { expect($interpolateProvider.endSymbol()).toBe('))'); })); it('should expose the endSymbol in run phase', inject(function($interpolate) { expect($interpolate.endSymbol()).toBe('))'); })); }); }); angular.js-1.2.11/test/ng/intervalSpec.js000066400000000000000000000174051227375216300202370ustar00rootroot00000000000000'use strict'; describe('$interval', function() { beforeEach(module(function($provide){ var repeatFns = [], nextRepeatId = 0, now = 0, $window; $window = { setInterval: function(fn, delay, count) { repeatFns.push({ nextTime:(now + delay), delay: delay, fn: fn, id: nextRepeatId, }); repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); return nextRepeatId++; }, clearInterval: function(id) { var fnIndex; angular.forEach(repeatFns, function(fn, index) { if (fn.id === id) fnIndex = index; }); if (fnIndex !== undefined) { repeatFns.splice(fnIndex, 1); return true; } return false; }, flush: function(millis) { now += millis; while (repeatFns.length && repeatFns[0].nextTime <= now) { var task = repeatFns[0]; task.fn(); task.nextTime += task.delay; repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); } return millis; } }; $provide.provider('$interval', $IntervalProvider); $provide.value('$window', $window); })); it('should run tasks repeatedly', inject(function($interval, $window) { var counter = 0; $interval(function() { counter++; }, 1000); expect(counter).toBe(0); $window.flush(1000) expect(counter).toBe(1); $window.flush(1000); expect(counter).toBe(2); })); it('should call $apply after each task is executed', inject(function($interval, $rootScope, $window) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(noop, 1000); expect(applySpy).not.toHaveBeenCalled(); $window.flush(1000); expect(applySpy).toHaveBeenCalledOnce(); applySpy.reset(); $interval(noop, 1000); $interval(noop, 1000); $window.flush(1000); expect(applySpy.callCount).toBe(3); })); it('should NOT call $apply if invokeApply is set to false', inject(function($interval, $rootScope, $window) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(noop, 1000, 0, false); expect(applySpy).not.toHaveBeenCalled(); $window.flush(2000); expect(applySpy).not.toHaveBeenCalled(); })); it('should allow you to specify the delay time', inject(function($interval, $window) { var counter = 0; $interval(function() { counter++; }, 123); expect(counter).toBe(0); $window.flush(122); expect(counter).toBe(0); $window.flush(1); expect(counter).toBe(1); })); it('should allow you to specify a number of iterations', inject(function($interval, $window) { var counter = 0; $interval(function() {counter++}, 1000, 2); $window.flush(1000); expect(counter).toBe(1); $window.flush(1000); expect(counter).toBe(2); $window.flush(1000); expect(counter).toBe(2); })); it('should return a promise which will be updated with the count on each iteration', inject(function($interval, $window) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $window.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $window.flush(1000); expect(log).toEqual(['tick', 'promise update: 0', 'tick', 'promise update: 1']); })); it('should return a promise which will be resolved after the specified number of iterations', inject(function($interval, $window) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000, 2); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $window.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $window.flush(1000); expect(log).toEqual([ 'tick', 'promise update: 0', 'tick', 'promise update: 1', 'promise success: 2']); })); describe('exception handling', function() { beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); it('should delegate exception to the $exceptionHandler service', inject( function($interval, $exceptionHandler, $window) { $interval(function() { throw "Test Error"; }, 1000); expect($exceptionHandler.errors).toEqual([]); $window.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error"]); $window.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error", "Test Error"]); })); it('should call $apply even if an exception is thrown in callback', inject( function($interval, $rootScope, $window) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(function() { throw "Test Error"; }, 1000); expect(applySpy).not.toHaveBeenCalled(); $window.flush(1000); expect(applySpy).toHaveBeenCalled(); })); it('should still update the interval promise when an exception is thrown', inject(function($interval, $window) { var log = [], promise = $interval(function() { throw "Some Error"; }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); $window.flush(1000); expect(log).toEqual(['promise update: 0']); })); }); describe('cancel', function() { it('should cancel tasks', inject(function($interval, $window) { var task1 = jasmine.createSpy('task1', 1000), task2 = jasmine.createSpy('task2', 1000), task3 = jasmine.createSpy('task3', 1000), promise1, promise3; promise1 = $interval(task1, 200); $interval(task2, 1000); promise3 = $interval(task3, 333); $interval.cancel(promise3); $interval.cancel(promise1); $window.flush(1000); expect(task1).not.toHaveBeenCalled(); expect(task2).toHaveBeenCalledOnce(); expect(task3).not.toHaveBeenCalled(); })); it('should cancel the promise', inject(function($interval, $rootScope, $window) { var promise = $interval(noop, 1000), log = []; promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $window.flush(1000); $interval.cancel(promise); $window.flush(1000); $rootScope.$apply(); // For resolving the promise - // necessary since q uses $rootScope.evalAsync. expect(log).toEqual(['promise update: 0', 'promise error: canceled']); })); it('should return true if a task was successfully canceled', inject(function($interval, $window) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), promise1, promise2; promise1 = $interval(task1, 1000, 1); $window.flush(1000); promise2 = $interval(task2, 1000, 1); expect($interval.cancel(promise1)).toBe(false); expect($interval.cancel(promise2)).toBe(true); })); it('should not throw a runtime exception when given an undefined promise', inject(function($interval) { expect($interval.cancel()).toBe(false); })); }); }); angular.js-1.2.11/test/ng/localeSpec.js000066400000000000000000000027121227375216300176450ustar00rootroot00000000000000'use strict'; describe('$locale', function() { var $locale = new $LocaleProvider().$get(); it('should have locale id set to en-us', function() { expect($locale.id).toBe('en-us'); }); it('should have NUMBER_FORMATS', function() { var numberFormats = $locale.NUMBER_FORMATS; expect(numberFormats).toBeDefined(); expect(numberFormats.PATTERNS.length).toBe(2); angular.forEach(numberFormats.PATTERNS, function(pattern) { expect(pattern.minInt).toBeDefined(); expect(pattern.minFrac).toBeDefined(); expect(pattern.maxFrac).toBeDefined(); expect(pattern.posPre).toBeDefined(); expect(pattern.posSuf).toBeDefined(); expect(pattern.negPre).toBeDefined(); expect(pattern.negSuf).toBeDefined(); expect(pattern.gSize).toBeDefined(); expect(pattern.lgSize).toBeDefined(); }); }); it('should have DATETIME_FORMATS', function() { var datetime = $locale.DATETIME_FORMATS; expect(datetime).toBeDefined(); expect(datetime.DAY.length).toBe(7); expect(datetime.SHORTDAY.length).toBe(7); expect(datetime.SHORTMONTH.length).toBe(12); expect(datetime.MONTH.length).toBe(12); expect(datetime.AMPMS.length).toBe(2); }); it('should return correct plural types', function() { expect($locale.pluralCat(-1)).toBe('other'); expect($locale.pluralCat(0)).toBe('other'); expect($locale.pluralCat(2)).toBe('other'); expect($locale.pluralCat(1)).toBe('one'); }); }); angular.js-1.2.11/test/ng/locationSpec.js000066400000000000000000001417221227375216300202230ustar00rootroot00000000000000'use strict'; describe('$location', function() { var url; beforeEach(module(provideLog)); afterEach(function() { // link rewriting used in html5 mode on legacy browsers binds to document.onClick, so we need // to clean this up after each test. jqLite(document).off('click'); }); describe('File Protocol', function () { var urlParsingNodePlaceholder; beforeEach(inject(function ($sniffer) { if ($sniffer.msie) return; urlParsingNodePlaceholder = urlParsingNode; //temporarily overriding the DOM element //with output from IE, if not in IE urlParsingNode = { hash : "#/C:/", host : "", hostname : "", href : "file:///C:/base#!/C:/foo", pathname : "/C:/foo", port : "", protocol : "file:", search : "", setAttribute: angular.noop }; })); afterEach(inject(function ($sniffer) { if ($sniffer.msie) return; //reset urlParsingNode urlParsingNode = urlParsingNodePlaceholder; })); it('should not include the drive name in path() on WIN', function (){ //See issue #4680 for details url = new LocationHashbangUrl('file:///base', '#!'); url.$$parse('file:///base#!/foo?a=b&c#hash'); expect(url.path()).toBe('/foo'); }); it('should include the drive name if it was provided in the input url', function () { url = new LocationHashbangUrl('file:///base', '#!'); url.$$parse('file:///base#!/C:/foo?a=b&c#hash'); expect(url.path()).toBe('/C:/foo'); }); }); describe('NewUrl', function() { beforeEach(function() { url = new LocationHtml5Url('http://www.domain.com:9877/'); url.$$parse('http://www.domain.com:9877/path/b?search=a&b=c&d#hash'); }); it('should provide common getters', function() { expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#hash'); expect(url.protocol()).toBe('http'); expect(url.host()).toBe('www.domain.com'); expect(url.port()).toBe(9877); expect(url.path()).toBe('/path/b'); expect(url.search()).toEqual({search: 'a', b: 'c', d: true}); expect(url.hash()).toBe('hash'); expect(url.url()).toBe('/path/b?search=a&b=c&d#hash'); }); it('path() should change path', function() { url.path('/new/path'); expect(url.path()).toBe('/new/path'); expect(url.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash'); }); it('search() should accept string', function() { url.search('x=y&c'); expect(url.search()).toEqual({x: 'y', c: true}); expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?x=y&c#hash'); }); it('search() should accept object', function() { url.search({one: 1, two: true}); expect(url.search()).toEqual({one: 1, two: true}); expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two#hash'); }); it('search() should change single parameter', function() { url.search({id: 'old', preserved: true}); url.search('id', 'new'); expect(url.search()).toEqual({id: 'new', preserved: true}); }); it('search() should remove single parameter', function() { url.search({id: 'old', preserved: true}); url.search('id', null); expect(url.search()).toEqual({preserved: true}); }); it('search() should handle multiple value', function() { url.search('a&b'); expect(url.search()).toEqual({a: true, b: true}); url.search('a', null); expect(url.search()).toEqual({b: true}); url.search('b', undefined); expect(url.search()).toEqual({}); }); it('search() should handle single value', function() { url.search('ignore'); expect(url.search()).toEqual({ignore: true}); }); it('search() should throw error an incorrect argument', function() { expect(function() { url.search(null); }).toThrowMinErr('$location', 'isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); expect(function() { url.search(undefined); }).toThrowMinErr('$location', 'isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); }); it('hash() should change hash fragment', function() { url.hash('new-hash'); expect(url.hash()).toBe('new-hash'); expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#new-hash'); }); it('url() should change the path, search and hash', function() { url.url('/some/path?a=b&c=d#hhh'); expect(url.url()).toBe('/some/path?a=b&c=d#hhh'); expect(url.absUrl()).toBe('http://www.domain.com:9877/some/path?a=b&c=d#hhh'); expect(url.path()).toBe('/some/path'); expect(url.search()).toEqual({a: 'b', c: 'd'}); expect(url.hash()).toBe('hhh'); }); it('url() should change only hash when no search and path specified', function() { url.url('#some-hash'); expect(url.hash()).toBe('some-hash'); expect(url.url()).toBe('/path/b?search=a&b=c&d#some-hash'); expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#some-hash'); }); it('url() should change only search and hash when no path specified', function() { url.url('?a=b'); expect(url.search()).toEqual({a: 'b'}); expect(url.hash()).toBe(''); expect(url.path()).toBe('/path/b'); }); it('url() should reset search and hash when only path specified', function() { url.url('/new/path'); expect(url.path()).toBe('/new/path'); expect(url.search()).toEqual({}); expect(url.hash()).toBe(''); }); it('replace should set $$replace flag and return itself', function() { expect(url.$$replace).toBe(false); url.replace(); expect(url.$$replace).toBe(true); expect(url.replace()).toBe(url); }); it('should parse new url', function() { url = new LocationHtml5Url('http://host.com/'); url.$$parse('http://host.com/base'); expect(url.path()).toBe('/base'); url = new LocationHtml5Url('http://host.com/'); url.$$parse('http://host.com/base#'); expect(url.path()).toBe('/base'); }); it('should prefix path with forward-slash', function() { url = new LocationHtml5Url('http://server/'); url.path('b'); expect(url.path()).toBe('/b'); expect(url.absUrl()).toBe('http://server/b'); }); it('should set path to forward-slash when empty', function() { url = new LocationHtml5Url('http://server/'); url.$$parse('http://server/') expect(url.path()).toBe('/'); expect(url.absUrl()).toBe('http://server/'); }); it('setters should return Url object to allow chaining', function() { expect(url.path('/any')).toBe(url); expect(url.search('')).toBe(url); expect(url.hash('aaa')).toBe(url); expect(url.url('/some')).toBe(url); }); it('should not preserve old properties when parsing new url', function() { url.$$parse('http://www.domain.com:9877/a'); expect(url.path()).toBe('/a'); expect(url.search()).toEqual({}); expect(url.hash()).toBe(''); expect(url.absUrl()).toBe('http://www.domain.com:9877/a'); }); it('should not rewrite when hashbang url is not given', function() { initService(true, '!', true); inject( initBrowser('http://domain.com/base/a/b', '/base'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); } ); }); it('should prepend path with basePath', function() { url = new LocationHtml5Url('http://server/base/'); url.$$parse('http://server/base/abc?a'); expect(url.path()).toBe('/abc'); expect(url.search()).toEqual({a: true}); url.path('/new/path'); expect(url.absUrl()).toBe('http://server/base/new/path?a'); }); it('should throw error when invalid server url given', function() { url = new LocationHtml5Url('http://server.org/base/abc', '/base'); expect(function() { url.$$parse('http://other.server.org/path#/path'); }).toThrowMinErr('$location', 'ipthprfx', 'Invalid url "http://other.server.org/path#/path", missing path prefix "http://server.org/base/".'); }); it('should throw error when invalid base url given', function() { url = new LocationHtml5Url('http://server.org/base/abc', '/base'); expect(function() { url.$$parse('http://server.org/path#/path'); }).toThrowMinErr('$location', 'ipthprfx', 'Invalid url "http://server.org/path#/path", missing path prefix "http://server.org/base/".'); }); describe('encoding', function() { it('should encode special characters', function() { url.path('/a <>#'); url.search({'i j': '<>#'}); url.hash('<>#'); expect(url.path()).toBe('/a <>#'); expect(url.search()).toEqual({'i j': '<>#'}); expect(url.hash()).toBe('<>#'); expect(url.absUrl()).toBe('http://www.domain.com:9877/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23'); }); it('should not encode !$:@', function() { url.path('/!$:@'); url.search(''); url.hash('!$:@'); expect(url.absUrl()).toBe('http://www.domain.com:9877/!$:@#!$:@'); }); it('should decode special characters', function() { url = new LocationHtml5Url('http://host.com/'); url.$$parse('http://host.com/a%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23'); expect(url.path()).toBe('/a <>#'); expect(url.search()).toEqual({'i j': '<>#'}); expect(url.hash()).toBe('x <>#'); }); }); }); describe('HashbangUrl', function() { beforeEach(function() { url = new LocationHashbangUrl('http://www.server.org:1234/base', '#!'); url.$$parse('http://www.server.org:1234/base#!/path?a=b&c#hash'); }); it('should parse hashbang url into path and search', function() { expect(url.protocol()).toBe('http'); expect(url.host()).toBe('www.server.org'); expect(url.port()).toBe(1234); expect(url.path()).toBe('/path'); expect(url.search()).toEqual({a: 'b', c: true}); expect(url.hash()).toBe('hash'); }); it('absUrl() should return hashbang url', function() { expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/path?a=b&c#hash'); url.path('/new/path'); url.search({one: 1}); url.hash('hhh'); expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/new/path?one=1#hhh'); }); it('should preserve query params in base', function() { url = new LocationHashbangUrl('http://www.server.org:1234/base?base=param', '#'); url.$$parse('http://www.server.org:1234/base?base=param#/path?a=b&c#hash'); expect(url.absUrl()).toBe('http://www.server.org:1234/base?base=param#/path?a=b&c#hash'); url.path('/new/path'); url.search({one: 1}); url.hash('hhh'); expect(url.absUrl()).toBe('http://www.server.org:1234/base?base=param#/new/path?one=1#hhh'); }); it('should prefix path with forward-slash', function() { url = new LocationHashbangUrl('http://host.com/base', '#'); url.$$parse('http://host.com/base#path'); expect(url.path()).toBe('/path'); expect(url.absUrl()).toBe('http://host.com/base#/path'); url.path('wrong'); expect(url.path()).toBe('/wrong'); expect(url.absUrl()).toBe('http://host.com/base#/wrong'); }); it('should set path to forward-slash when empty', function() { url = new LocationHashbangUrl('http://server/base', '#!'); url.$$parse('http://server/base'); url.path('aaa'); expect(url.path()).toBe('/aaa'); expect(url.absUrl()).toBe('http://server/base#!/aaa'); }); it('should not preserve old properties when parsing new url', function() { url.$$parse('http://www.server.org:1234/base#!/'); expect(url.path()).toBe('/'); expect(url.search()).toEqual({}); expect(url.hash()).toBe(''); expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/'); }); it('should throw error when invalid hashbang prefix given', function() { expect(function() { url.$$parse('http://www.server.org:1234/base#/path'); }).toThrowMinErr('$location', 'ihshprfx', 'Invalid url "http://www.server.org:1234/base#/path", missing hash prefix "#!".'); }); describe('encoding', function() { it('should encode special characters', function() { url.path('/a <>#'); url.search({'i j': '<>#'}); url.hash('<>#'); expect(url.path()).toBe('/a <>#'); expect(url.search()).toEqual({'i j': '<>#'}); expect(url.hash()).toBe('<>#'); expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23'); }); it('should not encode !$:@', function() { url.path('/!$:@'); url.search(''); url.hash('!$:@'); expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/!$:@#!$:@'); }); it('should decode special characters', function() { url = new LocationHashbangUrl('http://host.com/a', '#'); url.$$parse('http://host.com/a#/%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23'); expect(url.path()).toBe('/ <>#'); expect(url.search()).toEqual({'i j': '<>#'}); expect(url.hash()).toBe('x <>#'); }); it('should return decoded characters for search specified in URL', function() { var locationUrl = new LocationHtml5Url('http://host.com/'); locationUrl.$$parse('http://host.com/?q=1%2F2%203'); expect(locationUrl.search()).toEqual({'q': '1/2 3'}); }); it('should return decoded characters for search specified with setter', function() { var locationUrl = new LocationHtml5Url('http://host.com/'); locationUrl.$$parse('http://host.com/') locationUrl.search('q', '1/2 3'); expect(locationUrl.search()).toEqual({'q': '1/2 3'}); }); it('should return an array for duplicate params', function() { var locationUrl = new LocationHtml5Url('http://host.com'); locationUrl.$$parse('http://host.com') locationUrl.search('q', ['1/2 3','4/5 6']); expect(locationUrl.search()).toEqual({'q': ['1/2 3','4/5 6']}); }); it('should encode an array correctly from search and add to url', function() { var locationUrl = new LocationHtml5Url('http://host.com'); locationUrl.$$parse('http://host.com') locationUrl.search({'q': ['1/2 3','4/5 6']}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203&q=4%2F5%206'); }); it('should rewrite params when specifing a single param in search', function() { var locationUrl = new LocationHtml5Url('http://host.com'); locationUrl.$$parse('http://host.com') locationUrl.search({'q': '1/2 3'}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203'); locationUrl.search({'q': '4/5 6'}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206'); }); }); }); function initService(html5Mode, hashPrefix, supportHistory) { return module(function($provide, $locationProvider){ $locationProvider.html5Mode(html5Mode); $locationProvider.hashPrefix(hashPrefix); $provide.value('$sniffer', {history: supportHistory}); }); } function initBrowser(url, basePath) { return function($browser){ $browser.url(url); $browser.$$baseHref = basePath; }; } describe('wiring', function() { beforeEach(initService(false, '!', true)); beforeEach(inject(initBrowser('http://new.com/a/b#!', 'http://new.com/a/b'))); it('should update $location when browser url changes', inject(function($browser, $location) { spyOn($location, '$$parse').andCallThrough(); $browser.url('http://new.com/a/b#!/aaa'); $browser.poll(); expect($location.absUrl()).toBe('http://new.com/a/b#!/aaa'); expect($location.path()).toBe('/aaa'); expect($location.$$parse).toHaveBeenCalledOnce(); })); // location.href = '...' fires hashchange event synchronously, so it might happen inside $apply it('should not $apply when browser url changed inside $apply', inject( function($rootScope, $browser, $location) { var OLD_URL = $browser.url(), NEW_URL = 'http://new.com/a/b#!/new'; $rootScope.$apply(function() { $browser.url(NEW_URL); $browser.poll(); // simulate firing event from browser expect($location.absUrl()).toBe(OLD_URL); // should be async }); expect($location.absUrl()).toBe(NEW_URL); })); // location.href = '...' fires hashchange event synchronously, so it might happen inside $digest it('should not $apply when browser url changed inside $digest', inject( function($rootScope, $browser, $location) { var OLD_URL = $browser.url(), NEW_URL = 'http://new.com/a/b#!/new', notRunYet = true; $rootScope.$watch(function() { if (notRunYet) { notRunYet = false; $browser.url(NEW_URL); $browser.poll(); // simulate firing event from browser expect($location.absUrl()).toBe(OLD_URL); // should be async } }); $rootScope.$digest(); expect($location.absUrl()).toBe(NEW_URL); })); it('should update browser when $location changes', inject(function($rootScope, $browser, $location) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough(); $location.path('/new/path'); expect($browserUrl).not.toHaveBeenCalled(); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browser.url()).toBe('http://new.com/a/b#!/new/path'); })); it('should update browser only once per $apply cycle', inject(function($rootScope, $browser, $location) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough(); $location.path('/new/path'); $rootScope.$watch(function() { $location.search('a=b'); }); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browser.url()).toBe('http://new.com/a/b#!/new/path?a=b'); })); it('should replace browser url when url was replaced at least once', inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough(); $location.path('/n/url').replace(); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.mostRecentCall.args).toEqual(['http://new.com/a/b#!/n/url', true]); expect($location.$$replace).toBe(false); })); it('should always reset replace flag after running watch', inject(function($rootScope, $location) { // init watches $location.url('/initUrl'); $rootScope.$apply(); // changes url but resets it before digest $location.url('/newUrl').replace().url('/initUrl'); $rootScope.$apply(); expect($location.$$replace).toBe(false); // set the url to the old value $location.url('/newUrl').replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); // doesn't even change url only calls replace() $location.replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); })); it('should update the browser if changed from within a watcher', inject(function($rootScope, $location, $browser) { $rootScope.$watch(function() { return true; }, function() { $location.path('/changed'); }); $rootScope.$digest(); expect($browser.url()).toBe('http://new.com/a/b#!/changed'); })); }); // html5 history is disabled describe('disabled history', function() { it('should use hashbang url with hash prefix', function() { initService(false, '!'); inject( initBrowser('http://domain.com/base/index.html#!/a/b', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#!/new?a'); } ); }); it('should use hashbang url without hash prefix', function() { initService(false, ''); inject( initBrowser('http://domain.com/base/index.html#/a/b', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#/new?a'); } ); }); }); // html5 history enabled, but not supported by browser describe('history on old browser', function() { afterEach(inject(function($rootElement){ dealoc($rootElement); })); it('should use hashbang url with hash prefix', function() { initService(true, '!!', false); inject( initBrowser('http://domain.com/base/index.html#!!/a/b', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#!!/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#!!/new?a'); } ); }); it('should redirect to hashbang url when new url given', function() { initService(true, '!'); inject( initBrowser('http://domain.com/base/new-path/index.html', '/base/index.html'), function($browser, $location) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/new-path/index.html'); } ); }); it('should correctly convert html5 url with path matching basepath to hashbang url', function () { initService(true, '!', false); inject( initBrowser('http://domain.com/base/index.html', '/base/index.html'), function($browser, $location) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/index.html'); } ); }); }); // html5 history enabled and supported by browser describe('history on new browser', function() { afterEach(inject(function($rootElement){ dealoc($rootElement); })); it('should use new url', function() { initService(true, '', true); inject( initBrowser('http://domain.com/base/old/index.html#a', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/old/index.html#a'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/new?a#a'); } ); }); it('should rewrite when hashbang url given', function() { initService(true, '!', true); inject( initBrowser('http://domain.com/base/index.html#!/a/b', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); $location.path('/new'); $location.hash('abc'); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/new#abc'); expect($location.path()).toBe('/new'); } ); }); it('should rewrite when hashbang url given (without hash prefix)', function() { initService(true, '', true); inject( initBrowser('http://domain.com/base/index.html#/a/b', '/base/index.html'), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); expect($location.path()).toBe('/a/b'); } ); }); it('should set appBase to serverBase if base[href] is missing', function() { initService(true, '!', true); inject( initBrowser('http://domain.com/my/view1#anchor1', ''), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/my/view1#anchor1'); expect($location.path()).toBe('/my/view1'); expect($location.hash()).toBe('anchor1'); } ); }); }); describe('PATH_MATCH', function() { it('should parse just path', function() { var match = PATH_MATCH.exec('/path'); expect(match[1]).toBe('/path'); }); it('should parse path with search', function() { var match = PATH_MATCH.exec('/ppp/a?a=b&c'); expect(match[1]).toBe('/ppp/a'); expect(match[3]).toBe('a=b&c'); }); it('should parse path with hash', function() { var match = PATH_MATCH.exec('/ppp/a#abc?'); expect(match[1]).toBe('/ppp/a'); expect(match[5]).toBe('abc?'); }); it('should parse path with both search and hash', function() { var match = PATH_MATCH.exec('/ppp/a?a=b&c#abc/d?'); expect(match[3]).toBe('a=b&c'); }); }); describe('link rewriting', function() { var root, link, originalBrowser, lastEventPreventDefault; function configureService(linkHref, html5Mode, supportHist, attrs, content) { module(function($provide, $locationProvider) { attrs = attrs ? ' ' + attrs + ' ' : ''; // fake the base behavior if (linkHref[0] == '/') { linkHref = 'http://host.com' + linkHref; } else if(!linkHref.match(/:\/\//)) { linkHref = 'http://host.com/base/' + linkHref; } link = jqLite('' + content + '')[0]; $provide.value('$sniffer', {history: supportHist}); $locationProvider.html5Mode(html5Mode); $locationProvider.hashPrefix('!'); return function($rootElement, $document) { $rootElement.append(link); root = $rootElement[0]; // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); }; }); } function initBrowser() { return function($browser){ $browser.url('http://host.com/base'); $browser.$$baseHref = '/base/index.html'; }; } function initLocation() { return function($browser, $location, $rootElement) { originalBrowser = $browser.url(); // we have to prevent the default operation, as we need to test absolute links (http://...) // and navigating to these links would kill jstd $rootElement.on('click', function(e) { lastEventPreventDefault = e.isDefaultPrevented(); e.preventDefault(); }); }; } function expectRewriteTo($browser, url) { expect(lastEventPreventDefault).toBe(true); expect($browser.url()).toBe(url); } function expectNoRewrite($browser) { expect(lastEventPreventDefault).toBe(false); expect($browser.url()).toBe(originalBrowser); } afterEach(function() { dealoc(root); dealoc(document.body); }); it('should rewrite rel link to new url when history enabled on new browser', function() { configureService('link?a#b', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/link?a#b'); } ); }); it('should do nothing if already on the same URL', function() { configureService('/base/', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/'); jqLite(link).attr('href', 'http://host.com/base/foo'); browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/foo'); jqLite(link).attr('href', 'http://host.com/base/'); browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/'); jqLite(link). attr('href', 'http://host.com/base/foo'). on('click', function(e) { e.preventDefault(); }); browserTrigger(link, 'click'); expect($browser.url()).toBe('http://host.com/base/'); } ); }); it('should rewrite abs link to new url when history enabled on new browser', function() { configureService('/base/link?a#b', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/link?a#b'); } ); }); it('should rewrite rel link to hashbang url when history enabled on old browser', function() { configureService('link?a#b', true, false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/link?a#b'); } ); }); it('should rewrite abs link to hashbang url when history enabled on old browser', function() { configureService('/base/link?a#b', true, false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/link?a#b'); } ); }); it('should not rewrite full url links do different domain', function() { configureService('http://www.dot.abc/a?b=c', true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with target="_blank"', function() { configureService('/a?b=c', true, true, 'target="_blank"'); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with target specified', function() { configureService('/a?b=c', true, true, 'target="some-frame"'); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should rewrite full url links to same domain and base path', function() { configureService('http://host.com/base/new', true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/new'); } ); }); it('should rewrite when clicked span inside link', function() { configureService('some/link', true, true, '', 'link'); inject( initBrowser(), initLocation(), function($browser) { var span = jqLite(link).find('span'); browserTrigger(span, 'click'); expectRewriteTo($browser, 'http://host.com/base/some/link'); } ); }); it('should not rewrite when link to different base path when history enabled on new browser', function() { configureService('/other_base/link', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when link to different base path when history enabled on old browser', function() { configureService('/other_base/link', true, false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when link to different base path when history disabled', function() { configureService('/other_base/link', false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history enabled on new browser', function() { configureService('http://host.com/other_base/link', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history enabled on old browser', function() { configureService('http://host.com/other_base/link', true, false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history disabled', function() { configureService('http://host.com/other_base/link', false); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); // don't run next tests on IE<9, as browserTrigger does not simulate pressed keys if (!(msie < 9)) { it('should not rewrite when clicked with ctrl pressed', function() { configureService('/a?b=c', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click', ['ctrl']); expectNoRewrite($browser); } ); }); it('should not rewrite when clicked with meta pressed', function() { configureService('/a?b=c', true, true); inject( initBrowser(), initLocation(), function($browser) { browserTrigger(link, 'click', ['meta']); expectNoRewrite($browser); } ); }); } it('should not mess up hash urls when clicking on links in hashbang mode', function() { var base; module(function() { return function($browser) { window.location.hash = 'someHash'; base = window.location.href $browser.url(base); base = base.split('#')[0]; } }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var element = $compile('v1v2')($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); var av2 = $rootElement.find('a').eq(1); browserTrigger(av1, 'click'); expect($browser.url()).toEqual(base + '#/view1'); browserTrigger(av2, 'click'); expect($browser.url()).toEqual(base + '#/view2'); $rootElement.remove(); }); }); it('should not mess up hash urls when clicking on links in hashbang mode with a prefix', function() { var base; module(function($locationProvider) { return function($browser) { window.location.hash = '!someHash'; $browser.url(base = window.location.href); base = base.split('#')[0]; $locationProvider.hashPrefix('!'); } }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var element = $compile('v1v2')($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); var av2 = $rootElement.find('a').eq(1); browserTrigger(av1, 'click'); expect($browser.url()).toEqual(base + '#!/view1'); browserTrigger(av2, 'click'); expect($browser.url()).toEqual(base + '#!/view2'); }); }); it('should not intercept clicks outside the current hash prefix', function() { var base, clickHandler; module(function($provide) { $provide.value('$rootElement', { on: function(event, handler) { expect(event).toEqual('click'); clickHandler = handler; }, off: noop }); return function($browser) { $browser.url(base = 'http://server/'); } }); inject(function($location) { // make IE happy jqLite(window.document.body).html('link'); var event = { target: jqLite(window.document.body).find('a')[0], preventDefault: jasmine.createSpy('preventDefault') }; clickHandler(event); expect(event.preventDefault).not.toHaveBeenCalled(); }); }); it('should not intercept hash link clicks outside the app base url space', function() { var base, clickHandler; module(function($provide) { $provide.value('$rootElement', { on: function(event, handler) { expect(event).toEqual('click'); clickHandler = handler; }, off: angular.noop }); return function($browser) { $browser.url(base = 'http://server/'); } }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // make IE happy jqLite(window.document.body).html('link'); var event = { target: jqLite(window.document.body).find('a')[0], preventDefault: jasmine.createSpy('preventDefault') }; clickHandler(event); expect(event.preventDefault).not.toHaveBeenCalled(); }); }); // regression https://github.com/angular/angular.js/issues/1058 it('should not throw if element was removed', inject(function($document, $rootElement, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); $rootElement.html(''); var button = $rootElement.find('button'); button.on('click', function() { button.remove(); }); browserTrigger(button, 'click'); })); it('should not throw when clicking an SVGAElement link', function() { var base; module(function($locationProvider) { return function($browser) { window.location.hash = '!someHash'; $browser.url(base = window.location.href); base = base.split('#')[0]; $locationProvider.hashPrefix('!'); } }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var template = ''; var element = $compile(template)($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); expect(function() { browserTrigger(av1, 'click'); }).not.toThrow(); }); }); }); describe('location cancellation', function() { it('should fire $before/afterLocationChange event', inject(function($location, $browser, $rootScope, $log) { expect($browser.url()).toEqual('http://server/'); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); expect($location.url()).toEqual(''); $location.url('/somePath'); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/'); expect($log.info.logs).toEqual([]); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#/somePath', 'http://server/', 'http://server/#/somePath']); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/#/somePath'); })); it('should allow $locationChangeStart event cancellation', inject(function($location, $browser, $rootScope, $log) { expect($browser.url()).toEqual('http://server/'); expect($location.url()).toEqual(''); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); event.preventDefault(); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { throw Error('location should have been canceled'); }); expect($location.url()).toEqual(''); $location.url('/somePath'); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/'); expect($log.info.logs).toEqual([]); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']); expect($log.info.logs[1]).toBeUndefined(); expect($location.url()).toEqual(''); expect($browser.url()).toEqual('http://server/'); })); it ('should fire $locationChangeSuccess event when change from browser location bar', inject(function($log, $location, $browser, $rootScope) { $rootScope.$apply(); // clear initial $locationChangeStart expect($browser.url()).toEqual('http://server/'); expect($location.url()).toEqual(''); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('start', newUrl, oldUrl); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl); }); $browser.url('http://server/#/somePath'); $browser.poll(); expect($log.info.logs.shift()). toEqual(['start', 'http://server/#/somePath', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#/somePath', 'http://server/']); }) ); it('should listen on click events on href and prevent browser default in hashbang mode', function() { module(function() { return function($rootElement, $compile, $rootScope) { $rootElement.html('link'); $compile($rootElement)($rootScope); jqLite(document.body).append($rootElement); } }); inject(function($location, $rootScope, $browser, $rootElement) { var log = '', link = $rootElement.find('a'); $rootScope.$on('$locationChangeStart', function(event) { event.preventDefault(); log += '$locationChangeStart'; }); $rootScope.$on('$locationChangeSuccess', function() { throw new Error('after cancellation in hashbang mode'); }); browserTrigger(link, 'click'); expect(log).toEqual('$locationChangeStart'); expect($browser.url()).toEqual('http://server/'); dealoc($rootElement); }); }); it('should listen on click events on href and prevent browser default in html5 mode', function() { module(function($locationProvider) { $locationProvider.html5Mode(true); return function($rootElement, $compile, $rootScope) { $rootElement.html('link'); $compile($rootElement)($rootScope); jqLite(document.body).append($rootElement); } }); inject(function($location, $rootScope, $browser, $rootElement) { var log = '', link = $rootElement.find('a'), browserUrlBefore = $browser.url(); $rootScope.$on('$locationChangeStart', function(event) { event.preventDefault(); log += '$locationChangeStart'; }); $rootScope.$on('$locationChangeSuccess', function() { throw new Error('after cancellation in html5 mode'); }); browserTrigger(link, 'click'); expect(log).toEqual('$locationChangeStart'); expect($browser.url()).toBe(browserUrlBefore); dealoc($rootElement); }); }); it('should always return the new url value via path() when $locationChangeStart event occurs regardless of cause', inject(function($location, $rootScope, $browser, log) { var base = $browser.url(); $rootScope.$on('$locationChangeStart', function() { log($location.path()); }); // change through $location service $rootScope.$apply(function() { $location.path('/myNewPath'); }); // reset location $rootScope.$apply(function() { $location.path(''); }); // change through $browser $browser.url(base + '#/myNewPath'); $browser.poll(); expect(log).toEqual(['/myNewPath', '/', '/myNewPath']); }) ); }); describe('LocationHtml5Url', function() { var location, locationIndex; beforeEach(function() { location = new LocationHtml5Url('http://server/pre/', 'http://server/pre/path'); locationIndex = new LocationHtml5Url('http://server/pre/index.html', 'http://server/pre/path'); }); it('should rewrite URL', function() { expect(location.$$rewrite('http://other')).toEqual(undefined); expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/'); expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/'); expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath'); expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/'); expect(locationIndex.$$rewrite('http://server/pre/')).toEqual('http://server/pre/'); expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath'); }); }); describe('LocationHashbangUrl', function() { var location; it('should rewrite URL', function() { location = new LocationHashbangUrl('http://server/pre/', '#'); expect(location.$$rewrite('http://other')).toEqual(undefined); expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/'); expect(location.$$rewrite('http://server/pre/#otherPath')).toEqual('http://server/pre/#otherPath'); expect(location.$$rewrite('javascript:void(0)')).toEqual(undefined); }); it("should not set hash if one was not originally specified", function() { location = new LocationHashbangUrl('http://server/pre/index.html', '#'); location.$$parse('http://server/pre/index.html') expect(location.url()).toBe(''); expect(location.absUrl()).toBe('http://server/pre/index.html'); }); it("should parse hash if one was specified", function() { location = new LocationHashbangUrl('http://server/pre/index.html', '#'); location.$$parse('http://server/pre/index.html#/foo/bar') expect(location.url()).toBe('/foo/bar'); expect(location.absUrl()).toBe('http://server/pre/index.html#/foo/bar'); }); it("should prefix hash url with / if one was originally missing", function() { location = new LocationHashbangUrl('http://server/pre/index.html', '#'); location.$$parse('http://server/pre/index.html#not-starting-with-slash') expect(location.url()).toBe('/not-starting-with-slash'); expect(location.absUrl()).toBe('http://server/pre/index.html#/not-starting-with-slash'); }); }); describe('LocationHashbangInHtml5Url', function() { var location, locationIndex; beforeEach(function() { location = new LocationHashbangInHtml5Url('http://server/pre/', '#!'); locationIndex = new LocationHashbangInHtml5Url('http://server/pre/index.html', '#!'); }); it('should rewrite URL', function() { expect(location.$$rewrite('http://other')).toEqual(undefined); expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/'); expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/'); expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/#!otherPath'); expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/'); expect(locationIndex.$$rewrite('http://server/pre/')).toEqual(undefined); expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!otherPath'); }); }); }); angular.js-1.2.11/test/ng/logSpec.js000066400000000000000000000107521227375216300171720ustar00rootroot00000000000000'use strict'; function initService(debugEnabled) { return module(function($logProvider){ $logProvider.debugEnabled(debugEnabled); }); } describe('$log', function() { var $window, logger, log, warn, info, error, debug; beforeEach(module(function($provide){ $window = {navigator: {}, document: {}}; logger = ''; log = function() { logger+= 'log;'; }; warn = function() { logger+= 'warn;'; }; info = function() { logger+= 'info;'; }; error = function() { logger+= 'error;'; }; debug = function() { logger+= 'debug;'; }; $provide.provider('$log', $LogProvider); $provide.value('$exceptionHandler', angular.mock.rethrow); $provide.value('$window', $window); })); it('should use console if present', inject( function(){ $window.console = {log: log, warn: warn, info: info, error: error, debug: debug}; }, function($log) { $log.log(); $log.warn(); $log.info(); $log.error(); $log.debug(); expect(logger).toEqual('log;warn;info;error;debug;'); } )); it('should use console.log() if other not present', inject( function(){ $window.console = {log: log}; }, function($log) { $log.log(); $log.warn(); $log.info(); $log.error(); $log.debug(); expect(logger).toEqual('log;log;log;log;log;'); } )); it('should use noop if no console', inject( function($log) { $log.log(); $log.warn(); $log.info(); $log.error(); $log.debug(); } )); describe("IE logging behavior", function() { function removeApplyFunctionForIE() { log.apply = log.call = warn.apply = warn.call = info.apply = info.call = error.apply = error.call = debug.apply = debug.call = null; $window.console = {log: log, warn: warn, info: info, error: error, debug: debug}; } it("should work in IE where console.error doesn't have an apply method", inject( removeApplyFunctionForIE, function($log) { $log.log.apply($log); $log.warn.apply($log); $log.info.apply($log); $log.error.apply($log); $log.debug.apply($log); expect(logger).toEqual('log;warn;info;error;debug;'); }) ); it("should not attempt to log the second argument in IE if it is not specified", inject( function() { log = function(arg1, arg2) { logger+= 'log;' + arg2; }; warn = function(arg1, arg2) { logger+= 'warn;' + arg2; }; info = function(arg1, arg2) { logger+= 'info;' + arg2; }; error = function(arg1, arg2) { logger+= 'error;' + arg2; }; debug = function(arg1, arg2) { logger+= 'debug;' + arg2; }; }, removeApplyFunctionForIE, function($log) { $log.log(); $log.warn(); $log.info(); $log.error(); $log.debug(); expect(logger).toEqual('log;warn;info;error;debug;'); }) ); }); describe("$log.debug", function () { beforeEach(initService(false)); it("should skip debugging output if disabled", inject( function(){ $window.console = {log: log, warn: warn, info: info, error: error, debug: debug}; }, function($log) { $log.log(); $log.warn(); $log.info(); $log.error(); $log.debug(); expect(logger).toEqual('log;warn;info;error;'); } )); }); describe('$log.error', function() { var e, $log, errorArgs; beforeEach(function() { e = new Error(''); e.message = undefined; e.sourceURL = undefined; e.line = undefined; e.stack = undefined; $log = new $LogProvider().$get[1]({console:{error:function() { errorArgs = [].slice.call(arguments, 0); }}}); }); it('should pass error if does not have trace', function() { $log.error('abc', e); expect(errorArgs).toEqual(['abc', e]); }); it('should print stack', function() { e.stack = 'stack'; $log.error('abc', e); expect(errorArgs).toEqual(['abc', 'stack']); }); it('should print line', function() { e.message = 'message'; e.sourceURL = 'sourceURL'; e.line = '123'; $log.error('abc', e); expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']); }); }); }); angular.js-1.2.11/test/ng/parseSpec.js000066400000000000000000001576201227375216300175310ustar00rootroot00000000000000'use strict'; describe('parser', function() { beforeEach(function() { // clear caches getterFnCache = {}; promiseWarningCache = {}; }); describe('lexer', function() { var lex; beforeEach(function () { lex = function () { var lexer = new Lexer({csp: false, unwrapPromises: false}); return lexer.lex.apply(lexer, arguments); }; }); it('should tokenize a string', function() { var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\""); var i = 0; expect(tokens[i].index).toEqual(0); expect(tokens[i].text).toEqual('a.bc'); i++; expect(tokens[i].index).toEqual(4); expect(tokens[i].text).toEqual('['); i++; expect(tokens[i].index).toEqual(5); expect(tokens[i].text).toEqual(22); i++; expect(tokens[i].index).toEqual(7); expect(tokens[i].text).toEqual(']'); i++; expect(tokens[i].index).toEqual(8); expect(tokens[i].text).toEqual('+'); i++; expect(tokens[i].index).toEqual(9); expect(tokens[i].text).toEqual(1.3); i++; expect(tokens[i].index).toEqual(12); expect(tokens[i].text).toEqual('|'); i++; expect(tokens[i].index).toEqual(13); expect(tokens[i].text).toEqual('f'); i++; expect(tokens[i].index).toEqual(14); expect(tokens[i].text).toEqual(':'); i++; expect(tokens[i].index).toEqual(15); expect(tokens[i].string).toEqual("a'c"); i++; expect(tokens[i].index).toEqual(21); expect(tokens[i].text).toEqual(':'); i++; expect(tokens[i].index).toEqual(22); expect(tokens[i].string).toEqual('d"e'); }); it('should tokenize undefined', function() { var tokens = lex("undefined"); var i = 0; expect(tokens[i].index).toEqual(0); expect(tokens[i].text).toEqual('undefined'); expect(undefined).toEqual(tokens[i].fn()); }); it('should tokenize quoted string', function() { var str = "['\\'', \"\\\"\"]"; var tokens = lex(str); expect(tokens[1].index).toEqual(1); expect(tokens[1].string).toEqual("'"); expect(tokens[3].index).toEqual(7); expect(tokens[3].string).toEqual('"'); }); it('should tokenize escaped quoted string', function() { var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"'; var tokens = lex(str); expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0'); }); it('should tokenize unicode', function() { var tokens = lex('"\\u00A0"'); expect(tokens.length).toEqual(1); expect(tokens[0].string).toEqual('\u00a0'); }); it('should ignore whitespace', function() { var tokens = lex("a \t \n \r b"); expect(tokens[0].text).toEqual('a'); expect(tokens[1].text).toEqual('b'); }); it('should tokenize relation and equality', function() { var tokens = lex("! == != < > <= >= === !=="); expect(tokens[0].text).toEqual('!'); expect(tokens[1].text).toEqual('=='); expect(tokens[2].text).toEqual('!='); expect(tokens[3].text).toEqual('<'); expect(tokens[4].text).toEqual('>'); expect(tokens[5].text).toEqual('<='); expect(tokens[6].text).toEqual('>='); expect(tokens[7].text).toEqual('==='); expect(tokens[8].text).toEqual('!=='); }); it('should tokenize logical and ternary', function() { var tokens = lex("&& || ? :"); expect(tokens[0].text).toEqual('&&'); expect(tokens[1].text).toEqual('||'); expect(tokens[2].text).toEqual('?'); expect(tokens[3].text).toEqual(':'); }); it('should tokenize statements', function() { var tokens = lex("a;b;"); expect(tokens[0].text).toEqual('a'); expect(tokens[1].text).toEqual(';'); expect(tokens[2].text).toEqual('b'); expect(tokens[3].text).toEqual(';'); }); it('should tokenize function invocation', function() { var tokens = lex("a()"); expect(map(tokens, function(t) { return t.text;})).toEqual(['a', '(', ')']); }); it('should tokenize method invocation', function() { var tokens = lex("a.b.c (d) - e.f()"); expect(map(tokens, function(t) { return t.text;})). toEqual(['a.b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']); }); it('should tokenize number', function() { var tokens = lex("0.5"); expect(tokens[0].text).toEqual(0.5); }); it('should tokenize negative number', inject(function($rootScope) { var value = $rootScope.$eval("-0.5"); expect(value).toEqual(-0.5); value = $rootScope.$eval("{a:-0.5}"); expect(value).toEqual({a:-0.5}); })); it('should tokenize number with exponent', inject(function($rootScope) { var tokens = lex("0.5E-10"); expect(tokens[0].text).toEqual(0.5E-10); expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10); tokens = lex("0.5E+10"); expect(tokens[0].text).toEqual(0.5E+10); })); it('should throws exception for invalid exponent', function() { expect(function() { lex("0.5E-"); }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-].'); expect(function() { lex("0.5E-A"); }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].'); }); it('should tokenize number starting with a dot', function() { var tokens = lex(".5"); expect(tokens[0].text).toEqual(0.5); }); it('should throw error on invalid unicode', function() { expect(function() { lex("'\\u1''bla'"); }).toThrowMinErr("$parse", "lexerr", "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']."); }); }); var $filterProvider, scope; beforeEach(module(['$filterProvider', function (filterProvider) { $filterProvider = filterProvider; }])); forEach([true, false], function(cspEnabled) { forEach([true, false], function(unwrapPromisesEnabled) { describe('csp: ' + cspEnabled + ", unwrapPromises: " + unwrapPromisesEnabled, function() { var originalSecurityPolicy; beforeEach(function() { originalSecurityPolicy = window.document.securityPolicy; window.document.securityPolicy = {isActive : cspEnabled}; }); afterEach(function() { window.document.securityPolicy = originalSecurityPolicy; }); beforeEach(module(function ($parseProvider, $provide) { $parseProvider.unwrapPromises(unwrapPromisesEnabled); })); beforeEach(inject(function ($rootScope) { scope = $rootScope; })); it('should parse expressions', function() { expect(scope.$eval("-1")).toEqual(-1); expect(scope.$eval("1 + 2.5")).toEqual(3.5); expect(scope.$eval("1 + -2.5")).toEqual(-1.5); expect(scope.$eval("1+2*3/4")).toEqual(1+2*3/4); expect(scope.$eval("0--1+1.5")).toEqual(0- -1 + 1.5); expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0- -1+ +2*-3/-4); expect(scope.$eval("1/2*3")).toEqual(1/2*3); }); it('should parse comparison', function() { expect(scope.$eval("false")).toBeFalsy(); expect(scope.$eval("!true")).toBeFalsy(); expect(scope.$eval("1==1")).toBeTruthy(); expect(scope.$eval("1==true")).toBeTruthy(); expect(scope.$eval("1===1")).toBeTruthy(); expect(scope.$eval("1==='1'")).toBeFalsy(); expect(scope.$eval("1===true")).toBeFalsy(); expect(scope.$eval("'true'===true")).toBeFalsy(); expect(scope.$eval("1!==2")).toBeTruthy(); expect(scope.$eval("1!=='1'")).toBeTruthy(); expect(scope.$eval("1!=2")).toBeTruthy(); expect(scope.$eval("1<2")).toBeTruthy(); expect(scope.$eval("1<=1")).toBeTruthy(); expect(scope.$eval("1>2")).toEqual(1>2); expect(scope.$eval("2>=1")).toEqual(2>=1); expect(scope.$eval("true==2<3")).toEqual(true == 2<3); expect(scope.$eval("true===2<3")).toEqual(true === 2<3); }); it('should parse logical', function() { expect(scope.$eval("0&&2")).toEqual(0&&2); expect(scope.$eval("0||2")).toEqual(0||2); expect(scope.$eval("0||1&&2")).toEqual(0||1&&2); }); it('should parse ternary', function(){ var returnTrue = scope.returnTrue = function(){ return true; }; var returnFalse = scope.returnFalse = function(){ return false; }; var returnString = scope.returnString = function(){ return 'asd'; }; var returnInt = scope.returnInt = function(){ return 123; }; var identity = scope.identity = function(x){ return x; }; // Simple. expect(scope.$eval('0?0:2')).toEqual(0?0:2); expect(scope.$eval('1?0:2')).toEqual(1?0:2); // Nested on the left. expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2); expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2); expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2); expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2); expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3); expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2); expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2); expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3); expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3); // Nested on the right. expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2); expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2); expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2); expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2); expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3); expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2); expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2); expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3); expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3); // Precedence with respect to logical operators. expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1); expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0); expect(scope.$eval('0?0&&1:2')).toEqual(0?0&&1:2); expect(scope.$eval('0?1&&1:2')).toEqual(0?1&&1:2); expect(scope.$eval('0?0||0:1')).toEqual(0?0||0:1); expect(scope.$eval('0?0||1:2')).toEqual(0?0||1:2); expect(scope.$eval('1?0&&1:2')).toEqual(1?0&&1:2); expect(scope.$eval('1?1&&1:2')).toEqual(1?1&&1:2); expect(scope.$eval('1?0||0:1')).toEqual(1?0||0:1); expect(scope.$eval('1?0||1:2')).toEqual(1?0||1:2); expect(scope.$eval('0?1:0&&1')).toEqual(0?1:0&&1); expect(scope.$eval('0?2:1&&1')).toEqual(0?2:1&&1); expect(scope.$eval('0?1:0||0')).toEqual(0?1:0||0); expect(scope.$eval('0?2:0||1')).toEqual(0?2:0||1); expect(scope.$eval('1?1:0&&1')).toEqual(1?1:0&&1); expect(scope.$eval('1?2:1&&1')).toEqual(1?2:1&&1); expect(scope.$eval('1?1:0||0')).toEqual(1?1:0||0); expect(scope.$eval('1?2:0||1')).toEqual(1?2:0||1); // Function calls. expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt()); expect(scope.$eval('returnFalse() ? returnString() : returnInt()')).toEqual(returnFalse() ? returnString() : returnInt()); expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt()); expect(scope.$eval('identity(returnFalse() ? returnString() : returnInt())')).toEqual(identity(returnFalse() ? returnString() : returnInt())); }); it('should parse string', function() { expect(scope.$eval("'a' + 'b c'")).toEqual("ab c"); }); it('should parse filters', function() { $filterProvider.register('substring', valueFn(function(input, start, end) { return input.substring(start, end); })); expect(function() { scope.$eval("1|nonexistent"); }).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nonexistentFilterProvider <- nonexistentFilter'); scope.offset = 3; expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc"); expect(scope.$eval("'abcd'|substring:1:3|uppercase")).toEqual("BC"); }); it('should access scope', function() { scope.a = 123; scope.b = {c: 456}; expect(scope.$eval("a", scope)).toEqual(123); expect(scope.$eval("b.c", scope)).toEqual(456); expect(scope.$eval("x.y.z", scope)).not.toBeDefined(); }); it('should resolve deeply nested paths (important for CSP mode)', function() { scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}}; expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe('nooo!'); }); forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], function(pathLength) { it('should resolve nested paths of length ' + pathLength, function() { // Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}. var obj = 42, locals = {}; for (var i = pathLength; i >= 2; i--) { var newObj = {}; newObj['x' + i] = obj; obj = newObj; } // Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value. scope.x1 = obj; var path = 'x1'; for (var i = 2; i <= pathLength; i++) { path += '.x' + i; } expect(scope.$eval(path)).toBe(42); locals['x' + pathLength] = 'not 42' expect(scope.$eval(path, locals)).toBe(42); }); }); it('should be forgiving', function() { scope.a = {b: 23}; expect(scope.$eval('b')).toBeUndefined(); expect(scope.$eval('a.x')).toBeUndefined(); expect(scope.$eval('a.b.c.d')).toBeUndefined(); }); it('should support property names that collide with native object properties', function() { // regression scope.watch = 1; scope.toString = function toString() { return "custom toString"; }; expect(scope.$eval('watch', scope)).toBe(1); expect(scope.$eval('toString()', scope)).toBe('custom toString'); }); it('should not break if hasOwnProperty is referenced in an expression', function() { scope.obj = { value: 1}; // By evaluating an expression that calls hasOwnProperty, the getterFnCache // will store a property called hasOwnProperty. This is effectively: // getterFnCache['hasOwnProperty'] = null scope.$eval('obj.hasOwnProperty("value")'); // If we rely on this property then evaluating any expression will fail // because it is not able to find out if obj.value is there in the cache expect(scope.$eval('obj.value')).toBe(1); }); it('should not break if the expression is "hasOwnProperty"', function() { scope.fooExp = 'barVal'; // By evaluating hasOwnProperty, the $parse cache will store a getter for // the scope's own hasOwnProperty function, which will mess up future cache look ups. // i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; } scope.$eval('hasOwnProperty'); expect(scope.$eval('fooExp')).toBe('barVal'); }); it('should evaluate grouped expressions', function() { expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3); }); it('should evaluate assignments', function() { expect(scope.$eval("a=12")).toEqual(12); expect(scope.a).toEqual(12); expect(scope.$eval("x.y.z=123;")).toEqual(123); expect(scope.x.y.z).toEqual(123); expect(scope.$eval("a=123; b=234")).toEqual(234); expect(scope.a).toEqual(123); expect(scope.b).toEqual(234); }); it('should evaluate function call without arguments', function() { scope['const'] = function(a,b){return 123;}; expect(scope.$eval("const()")).toEqual(123); }); it('should evaluate function call with arguments', function() { scope.add = function(a,b) { return a+b; }; expect(scope.$eval("add(1,2)")).toEqual(3); }); it('should evaluate function call from a return value', function() { scope.val = 33; scope.getter = function() { return function() { return this.val; }}; expect(scope.$eval("getter()()")).toBe(33); }); it('should evaluate multiplication and division', function() { scope.taxRate = 8; scope.subTotal = 100; expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8); expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8); }); it('should evaluate array', function() { expect(scope.$eval("[]").length).toEqual(0); expect(scope.$eval("[1, 2]").length).toEqual(2); expect(scope.$eval("[1, 2]")[0]).toEqual(1); expect(scope.$eval("[1, 2]")[1]).toEqual(2); }); it('should evaluate array access', function() { expect(scope.$eval("[1][0]")).toEqual(1); expect(scope.$eval("[[1]][0][0]")).toEqual(1); expect(scope.$eval("[].length")).toEqual(0); expect(scope.$eval("[1, 2].length")).toEqual(2); }); it('should evaluate object', function() { expect(toJson(scope.$eval("{}"))).toEqual("{}"); expect(toJson(scope.$eval("{a:'b'}"))).toEqual('{"a":"b"}'); expect(toJson(scope.$eval("{'a':'b'}"))).toEqual('{"a":"b"}'); expect(toJson(scope.$eval("{\"a\":'b'}"))).toEqual('{"a":"b"}'); }); it('should evaluate object access', function() { expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC"); }); it('should evaluate JSON', function() { expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]"); expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]'); }); it('should evaluate multiple statements', function() { expect(scope.$eval("a=1;b=3;a+b")).toEqual(4); expect(scope.$eval(";;1;;")).toEqual(1); }); it('should evaluate object methods in correct context (this)', function() { var C = function () { this.a = 123; }; C.prototype.getA = function() { return this.a; }; scope.obj = new C(); expect(scope.$eval("obj.getA()")).toEqual(123); expect(scope.$eval("obj['getA']()")).toEqual(123); }); it('should evaluate methods in correct context (this) in argument', function() { var C = function () { this.a = 123; }; C.prototype.sum = function(value) { return this.a + value; }; C.prototype.getA = function() { return this.a; }; scope.obj = new C(); expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246); expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246); }); it('should evaluate objects on scope context', function() { scope.a = "abc"; expect(scope.$eval("{a:a}").a).toEqual("abc"); }); it('should evaluate field access on function call result', function() { scope.a = function() { return {name:'misko'}; }; expect(scope.$eval("a().name")).toEqual("misko"); }); it('should evaluate field access after array access', function () { scope.items = [{}, {name:'misko'}]; expect(scope.$eval('items[1].name')).toEqual("misko"); }); it('should evaluate array assignment', function() { scope.items = []; expect(scope.$eval('items[1] = "abc"')).toEqual("abc"); expect(scope.$eval('items[1]')).toEqual("abc"); // Dont know how to make this work.... // expect(scope.$eval('books[1] = "moby"')).toEqual("moby"); // expect(scope.$eval('books[1]')).toEqual("moby"); }); it('should evaluate grouped filters', function() { scope.name = 'MISKO'; expect(scope.$eval('n = (name|lowercase)')).toEqual('misko'); expect(scope.$eval('n')).toEqual('misko'); }); it('should evaluate remainder', function() { expect(scope.$eval('1%2')).toEqual(1); }); it('should evaluate sum with undefined', function() { expect(scope.$eval('1+undefined')).toEqual(1); expect(scope.$eval('undefined+1')).toEqual(1); }); it('should throw exception on non-closed bracket', function() { expect(function() { scope.$eval('[].count('); }).toThrowMinErr('$parse', 'ueoe', 'Unexpected end of expression: [].count('); }); it('should evaluate double negation', function() { expect(scope.$eval('true')).toBeTruthy(); expect(scope.$eval('!true')).toBeFalsy(); expect(scope.$eval('!!true')).toBeTruthy(); expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a'); }); it('should evaluate negation', function() { expect(scope.$eval("!false || true")).toEqual(!false || true); expect(scope.$eval("!11 == 10")).toEqual(!11 == 10); expect(scope.$eval("12/6/2")).toEqual(12/6/2); }); it('should evaluate exclamation mark', function() { expect(scope.$eval('suffix = "!"')).toEqual('!'); }); it('should evaluate minus', function() { expect(scope.$eval("{a:'-'}")).toEqual({a: "-"}); }); it('should evaluate undefined', function() { expect(scope.$eval("undefined")).not.toBeDefined(); expect(scope.$eval("a=undefined")).not.toBeDefined(); expect(scope.a).not.toBeDefined(); }); it('should allow assignment after array dereference', function() { scope.obj = [{}]; scope.$eval('obj[0].name=1'); expect(scope.obj.name).toBeUndefined(); expect(scope.obj[0].name).toEqual(1); }); it('should short-circuit AND operator', function() { scope.run = function() { throw "IT SHOULD NOT HAVE RUN"; }; expect(scope.$eval('false && run()')).toBe(false); }); it('should short-circuit OR operator', function() { scope.run = function() { throw "IT SHOULD NOT HAVE RUN"; }; expect(scope.$eval('true || run()')).toBe(true); }); it('should support method calls on primitive types', function() { scope.empty = ''; scope.zero = 0; scope.bool = false; expect(scope.$eval('empty.substr(0)')).toBe(''); expect(scope.$eval('zero.toString()')).toBe('0'); expect(scope.$eval('bool.toString()')).toBe('false'); }); it('should evaluate expressions with line terminators', function() { scope.a = "a"; scope.b = {c: "bc"}; expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n"); }); describe('sandboxing', function() { describe('Function constructor', function() { it('should NOT allow access to Function constructor in getter', function() { expect(function() { scope.$eval('{}.toString.constructor'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor'); expect(function() { scope.$eval('{}.toString.constructor("alert(1)")'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor("alert(1)")'); expect(function() { scope.$eval('[].toString.constructor.foo'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: [].toString.constructor.foo'); expect(function() { scope.$eval('{}.toString["constructor"]'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString["constructor"]'); expect(function() { scope.$eval('{}["toString"]["constructor"]'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}["toString"]["constructor"]'); scope.a = []; expect(function() { scope.$eval('a.toString.constructor', scope); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: a.toString.constructor'); expect(function() { scope.$eval('a.toString["constructor"]', scope); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: a.toString["constructor"]'); }); it('should NOT allow access to Function constructor in setter', function() { expect(function() { scope.$eval('{}.toString.constructor = 1'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor = 1'); expect(function() { scope.$eval('{}.toString.constructor.a = 1'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor.a = 1'); expect(function() { scope.$eval('{}.toString["constructor"]["constructor"] = 1'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString["constructor"]["constructor"] = 1'); scope.key1 = "const"; scope.key2 = "ructor"; expect(function() { scope.$eval('{}.toString[key1 + key2].foo = 1'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString[key1 + key2].foo = 1'); expect(function() { scope.$eval('{}.toString["constructor"]["a"] = 1'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString["constructor"]["a"] = 1'); scope.a = []; expect(function() { scope.$eval('a.toString.constructor = 1', scope); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: a.toString.constructor = 1'); }); it('should NOT allow access to Function constructor that has been aliased', function() { scope.foo = { "bar": Function }; expect(function() { scope.$eval('foo["bar"]'); }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: foo["bar"]'); }); it('should NOT allow access to Function constructor in getter', function() { expect(function() { scope.$eval('{}.toString.constructor'); }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor'); }); }); describe('Window and $element/node', function() { it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) { scope.wrap = {w: $window, d: $document}; expect(function() { scope.$eval('wrap["w"]', scope); }).toThrowMinErr( '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + 'disallowed! Expression: wrap["w"]'); expect(function() { scope.$eval('wrap["d"]', scope); }).toThrowMinErr( '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + 'disallowed! Expression: wrap["d"]'); })); it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) { scope.getWin = valueFn($window); scope.getDoc = valueFn($document); expect(function() { scope.$eval('getWin()', scope); }).toThrowMinErr( '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + 'disallowed! Expression: getWin()'); expect(function() { scope.$eval('getDoc()', scope); }).toThrowMinErr( '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + 'disallowed! Expression: getDoc()'); })); it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) { scope.a = {b: { win: $window, doc: $document }}; expect(function() { scope.$eval('a.b.win.alert(1)', scope); }).toThrowMinErr( '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + 'disallowed! Expression: a.b.win.alert(1)'); expect(function() { scope.$eval('a.b.doc.on("click")', scope); }).toThrowMinErr( '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + 'disallowed! Expression: a.b.doc.on("click")'); })); }); }); describe('overriding constructor', function() { it('should evaluate grouped expressions', function() { scope.foo = function foo() { return "foo"; }; // When not overridden, access should be restricted both by the dot operator and by the // index operator. expect(function() { scope.$eval('foo.constructor()', scope) }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: foo.constructor()'); expect(function() { scope.$eval('foo["constructor"]()', scope) }).toThrowMinErr( '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: foo["constructor"]()'); // User defined value assigned to constructor. scope.foo.constructor = function constructor() { return "custom constructor"; }; // Dot operator should still block it. expect(function() { scope.$eval('foo.constructor()', scope) }).toThrowMinErr( '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + 'Expression: foo.constructor()'); // However, the index operator should allow it. expect(scope.$eval('foo["constructor"]()', scope)).toBe('custom constructor'); }); }); it('should call the function from the received instance and not from a new one', function() { var n = 0; scope.fn = function() { var c = n++; return { c: c, anotherFn: function() { return this.c == c; } }; }; expect(scope.$eval('fn().anotherFn()')).toBe(true); }); it('should call the function once when it is part of the context', function() { var count = 0; scope.fn = function() { count++; return { anotherFn: function() { return "lucas"; } }; }; expect(scope.$eval('fn().anotherFn()')).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is not part of the context', function() { var count = 0; scope.fn = function() { count++; return function() { return 'lucas'; }; }; expect(scope.$eval('fn()()')).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is part of the context on assignments', function() { var count = 0; var element = {}; scope.fn = function() { count++; return element; }; expect(scope.$eval('fn().name = "lucas"')).toBe('lucas'); expect(element.name).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is part of the context on array lookups', function() { var count = 0; var element = []; scope.fn = function() { count++; return element; }; expect(scope.$eval('fn()[0] = "lucas"')).toBe('lucas'); expect(element[0]).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is part of the context on array lookup function', function() { var count = 0; var element = [{anotherFn: function() { return 'lucas';} }]; scope.fn = function() { count++; return element; }; expect(scope.$eval('fn()[0].anotherFn()')).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is part of the context on property lookup function', function() { var count = 0; var element = {name: {anotherFn: function() { return 'lucas';} } }; scope.fn = function() { count++; return element; }; expect(scope.$eval('fn().name.anotherFn()')).toBe('lucas'); expect(count).toBe(1); }); it('should call the function once when it is part of a sub-expression', function() { var count = 0; scope.element = [{}]; scope.fn = function() { count++; return 0; }; expect(scope.$eval('element[fn()].name = "lucas"')).toBe('lucas'); expect(scope.element[0].name).toBe('lucas'); expect(count).toBe(1); }); describe('assignable', function() { it('should expose assignment function', inject(function($parse) { var fn = $parse('a'); expect(fn.assign).toBeTruthy(); var scope = {}; fn.assign(scope, 123); expect(scope).toEqual({a:123}); })); }); describe('locals', function() { it('should expose local variables', inject(function($parse) { expect($parse('a')({a: 0}, {a: 1})).toEqual(1); expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3); })); it('should expose traverse locals', inject(function($parse) { expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1); expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1); expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined); expect($parse('a.b.c')({a: null}, {a: {b: {c: 1}}})).toEqual(1); })); it('should not use locals to resolve object properties', inject(function($parse) { expect($parse('a[0].b')({a: [ {b : 'scope'} ]}, {b : 'locals'})).toBe('scope'); expect($parse('a[0]["b"]')({a: [ {b : 'scope'} ]}, {b : 'locals'})).toBe('scope'); expect($parse('a[0][0].b')({a: [[{b : 'scope'}]]}, {b : 'locals'})).toBe('scope'); expect($parse('a[0].b.c')({a: [ {b: {c: 'scope'}}] }, {b : {c: 'locals'} })).toBe('scope'); })); }); describe('literal', function() { it('should mark scalar value expressions as literal', inject(function($parse) { expect($parse('0').literal).toBe(true); expect($parse('"hello"').literal).toBe(true); expect($parse('true').literal).toBe(true); expect($parse('false').literal).toBe(true); expect($parse('null').literal).toBe(true); expect($parse('undefined').literal).toBe(true); })); it('should mark array expressions as literal', inject(function($parse) { expect($parse('[]').literal).toBe(true); expect($parse('[1, 2, 3]').literal).toBe(true); expect($parse('[1, identifier]').literal).toBe(true); })); it('should mark object expressions as literal', inject(function($parse) { expect($parse('{}').literal).toBe(true); expect($parse('{x: 1}').literal).toBe(true); expect($parse('{foo: bar}').literal).toBe(true); })); it('should not mark function calls or operator expressions as literal', inject(function($parse) { expect($parse('1 + 1').literal).toBe(false); expect($parse('call()').literal).toBe(false); expect($parse('[].length').literal).toBe(false); })); }); describe('constant', function() { it('should mark scalar value expressions as constant', inject(function($parse) { expect($parse('12.3').constant).toBe(true); expect($parse('"string"').constant).toBe(true); expect($parse('true').constant).toBe(true); expect($parse('false').constant).toBe(true); expect($parse('null').constant).toBe(true); expect($parse('undefined').constant).toBe(true); })); it('should mark arrays as constant if they only contain constant elements', inject(function($parse) { expect($parse('[]').constant).toBe(true); expect($parse('[1, 2, 3]').constant).toBe(true); expect($parse('["string", null]').constant).toBe(true); expect($parse('[[]]').constant).toBe(true); expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true); })); it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) { expect($parse('[foo]').constant).toBe(false); expect($parse('[x + 1]').constant).toBe(false); expect($parse('[bar[0]]').constant).toBe(false); })); it('should mark complex expressions involving constant values as constant', inject(function($parse) { expect($parse('!true').constant).toBe(true); expect($parse('1 - 1').constant).toBe(true); expect($parse('"foo" + "bar"').constant).toBe(true); expect($parse('5 != null').constant).toBe(true); expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true); })); it('should not mark any expression involving variables or function calls as constant', inject(function($parse) { expect($parse('true.toString()').constant).toBe(false); expect($parse('foo(1, 2, 3)').constant).toBe(false); expect($parse('"name" + id').constant).toBe(false); })); }); describe('nulls in expressions', function() { // simpleGetterFn1 it('should return null for `a` where `a` is null', inject(function($rootScope) { $rootScope.a = null; expect($rootScope.$eval('a')).toBe(null); })); it('should return undefined for `a` where `a` is undefined', inject(function($rootScope) { expect($rootScope.$eval('a')).toBeUndefined(); })); // simpleGetterFn2 it('should return undefined for properties of `null` constant', inject(function($rootScope) { expect($rootScope.$eval('null.a')).toBeUndefined(); })); it('should return undefined for properties of `null` values', inject(function($rootScope) { $rootScope.a = null; expect($rootScope.$eval('a.b')).toBeUndefined(); })); it('should return null for `a.b` where `b` is null', inject(function($rootScope) { $rootScope.a = { b: null }; expect($rootScope.$eval('a.b')).toBe(null); })); // cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2 it('should return null for `a.b.c.d.e` where `e` is null', inject(function($rootScope) { $rootScope.a = { b: { c: { d: { e: null } } } }; expect($rootScope.$eval('a.b.c.d.e')).toBe(null); })); it('should return undefined for `a.b.c.d.e` where `d` is null', inject(function($rootScope) { $rootScope.a = { b: { c: { d: null } } }; expect($rootScope.$eval('a.b.c.d.e')).toBeUndefined(); })); // cspSafeGetter || pathKeys.length > 6 it('should return null for `a.b.c.d.e.f.g` where `g` is null', inject(function($rootScope) { $rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } }; expect($rootScope.$eval('a.b.c.d.e.f.g')).toBe(null); })); it('should return undefined for `a.b.c.d.e.f.g` where `f` is null', inject(function($rootScope) { $rootScope.a = { b: { c: { d: { e: { f: null } } } } }; expect($rootScope.$eval('a.b.c.d.e.f.g')).toBeUndefined(); })); }); }); }); }); describe('promises', function() { var deferred, promise, q; describe('unwrapPromises setting', function () { beforeEach(inject(function($rootScope, $q) { scope = $rootScope; $rootScope.$apply(function() { deferred = $q.defer(); deferred.resolve('Bobo'); promise = deferred.promise; }); })); it('should not unwrap promises by default', inject(function ($parse) { scope.person = promise; scope.things = {person: promise}; scope.getPerson = function () { return promise; }; var getter = $parse('person'); var propGetter = $parse('things.person'); var fnGetter = $parse('getPerson()'); expect(getter(scope)).toBe(promise); expect(propGetter(scope)).toBe(promise); expect(fnGetter(scope)).toBe(promise); })); }); forEach([true, false], function(cspEnabled) { describe('promise logging (csp:' + cspEnabled + ')', function() { var $log; var PROMISE_WARNING_REGEXP = /\[\$parse\] Promise found in the expression `[^`]+`. Automatic unwrapping of promises in Angular expressions is deprecated\./; var originalSecurityPolicy; beforeEach(function() { originalSecurityPolicy = window.document.securityPolicy; window.document.securityPolicy = {isActive : cspEnabled}; }); afterEach(function() { window.document.securityPolicy = originalSecurityPolicy; }); beforeEach(module(function($parseProvider) { $parseProvider.unwrapPromises(true); })); beforeEach(inject(function($rootScope, $q, _$log_) { scope = $rootScope; $rootScope.$apply(function() { deferred = $q.defer(); deferred.resolve('Bobo'); promise = deferred.promise; }); $log = _$log_; })); it('should log warnings by default', function() { scope.person = promise; scope.$eval('person'); expect($log.warn.logs.pop()).toEqual(['[$parse] Promise found in the expression `person`. ' + 'Automatic unwrapping of promises in Angular expressions is deprecated.']); }); it('should log warnings for deep promises', function() { scope.car = {wheel: {disc: promise}}; scope.$eval('car.wheel.disc.pad'); expect($log.warn.logs.pop()).toMatch(PROMISE_WARNING_REGEXP); }); it('should log warnings for setters', function() { scope.person = promise; scope.$eval('person.name = "Bubu"'); expect($log.warn.logs.pop()).toMatch(PROMISE_WARNING_REGEXP); }); it('should log only a single warning for each expression', function() { scope.person1 = promise; scope.person2 = promise; scope.$eval('person1'); scope.$eval('person1'); expect($log.warn.logs.pop()).toMatch(/`person1`/); expect($log.warn.logs).toEqual([]); scope.$eval('person1'); scope.$eval('person2'); scope.$eval('person1'); scope.$eval('person2'); expect($log.warn.logs.pop()).toMatch(/`person2`/); expect($log.warn.logs).toEqual([]); }); it('should log warning for complex expressions', function() { scope.person1 = promise; scope.person2 = promise; scope.$eval('person1 + person2'); expect($log.warn.logs.pop()).toMatch(/`person1 \+ person2`/); expect($log.warn.logs).toEqual([]); }); }); }); forEach([true, false], function(cspEnabled) { describe('csp ' + cspEnabled, function() { var originalSecurityPolicy; beforeEach(function() { originalSecurityPolicy = window.document.securityPolicy; window.document.securityPolicy = {isActive : cspEnabled}; }); afterEach(function() { window.document.securityPolicy = originalSecurityPolicy; }); beforeEach(module(function($parseProvider) { $parseProvider.unwrapPromises(true); $parseProvider.logPromiseWarnings(false); })); beforeEach(inject(function($rootScope, $q) { scope = $rootScope; q = $q; deferred = q.defer(); promise = deferred.promise; })); describe('{{promise}}', function() { it('should evaluated resolved promise and get its value', function() { deferred.resolve('hello!'); scope.greeting = promise; expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe('hello!'); }); it('should evaluated rejected promise and ignore the rejection reason', function() { deferred.reject('sorry'); scope.greeting = promise; expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe(undefined); }); it('should evaluate a promise and eventualy get its value', function() { scope.greeting = promise; expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe(undefined); deferred.resolve('hello!'); expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe('hello!'); }); it('should evaluate a promise and eventualy ignore its rejection', function() { scope.greeting = promise; expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe(undefined); deferred.reject('sorry'); expect(scope.$eval('greeting')).toBe(undefined); scope.$digest(); expect(scope.$eval('greeting')).toBe(undefined); }); describe('assignment into promises', function() { // This behavior is analogous to assignments to non-promise values // that are lazily set on the scope. it('should evaluate a resolved object promise and set its value', inject(function($parse) { scope.person = promise; deferred.resolve({'name': 'Bill Gates'}); var getter = $parse('person.name', { unwrapPromises: true }); expect(getter(scope)).toBe(undefined); scope.$digest(); expect(getter(scope)).toBe('Bill Gates'); getter.assign(scope, 'Warren Buffet'); expect(getter(scope)).toBe('Warren Buffet'); })); it('should evaluate a resolved primitive type promise and set its value', inject(function($parse) { scope.greeting = promise; deferred.resolve('Salut!'); var getter = $parse('greeting', { unwrapPromises: true }); expect(getter(scope)).toBe(undefined); scope.$digest(); expect(getter(scope)).toBe('Salut!'); getter.assign(scope, 'Bonjour'); expect(getter(scope)).toBe('Bonjour'); })); it('should evaluate an unresolved promise and set and remember its value', inject(function($parse) { scope.person = promise; var getter = $parse('person.name', { unwrapPromises: true }); expect(getter(scope)).toBe(undefined); scope.$digest(); expect(getter(scope)).toBe(undefined); getter.assign(scope, 'Bonjour'); scope.$digest(); expect(getter(scope)).toBe('Bonjour'); var c1Getter = $parse('person.A.B.C1', { unwrapPromises: true }); scope.$digest(); expect(c1Getter(scope)).toBe(undefined); c1Getter.assign(scope, 'c1_value'); scope.$digest(); expect(c1Getter(scope)).toBe('c1_value'); // Set another property on the person.A.B var c2Getter = $parse('person.A.B.C2', { unwrapPromises: true }); scope.$digest(); expect(c2Getter(scope)).toBe(undefined); c2Getter.assign(scope, 'c2_value'); scope.$digest(); expect(c2Getter(scope)).toBe('c2_value'); // c1 should be unchanged. expect($parse('person.A', { unwrapPromises: true })(scope)).toEqual( {B: {C1: 'c1_value', C2: 'c2_value'}}); })); it('should evaluate a resolved promise and overwrite the previous set value in the absense of the getter', inject(function($parse) { scope.person = promise; var c1Getter = $parse('person.A.B.C1', { unwrapPromises: true }); c1Getter.assign(scope, 'c1_value'); // resolving the promise should update the tree. deferred.resolve({A: {B: {C1: 'resolved_c1'}}}); scope.$digest(); expect(c1Getter(scope)).toEqual('resolved_c1'); })); }); }); describe('dereferencing', function() { it('should evaluate and dereference properties leading to and from a promise', function() { scope.obj = {greeting: promise}; expect(scope.$eval('obj.greeting')).toBe(undefined); expect(scope.$eval('obj.greeting.polite')).toBe(undefined); scope.$digest(); expect(scope.$eval('obj.greeting')).toBe(undefined); expect(scope.$eval('obj.greeting.polite')).toBe(undefined); deferred.resolve({polite: 'Good morning!'}); scope.$digest(); expect(scope.$eval('obj.greeting')).toEqual({polite: 'Good morning!'}); expect(scope.$eval('obj.greeting.polite')).toBe('Good morning!'); }); it('should evaluate and dereference properties leading to and from a promise via bracket ' + 'notation', function() { scope.obj = {greeting: promise}; expect(scope.$eval('obj["greeting"]')).toBe(undefined); expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined); scope.$digest(); expect(scope.$eval('obj["greeting"]')).toBe(undefined); expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined); deferred.resolve({polite: 'Good morning!'}); scope.$digest(); expect(scope.$eval('obj["greeting"]')).toEqual({polite: 'Good morning!'}); expect(scope.$eval('obj["greeting"]["polite"]')).toBe('Good morning!'); }); it('should evaluate and dereference array references leading to and from a promise', function() { scope.greetings = [promise]; expect(scope.$eval('greetings[0]')).toBe(undefined); expect(scope.$eval('greetings[0][0]')).toBe(undefined); scope.$digest(); expect(scope.$eval('greetings[0]')).toBe(undefined); expect(scope.$eval('greetings[0][0]')).toBe(undefined); deferred.resolve(['Hi!', 'Cau!']); scope.$digest(); expect(scope.$eval('greetings[0]')).toEqual(['Hi!', 'Cau!']); expect(scope.$eval('greetings[0][0]')).toBe('Hi!'); }); it('should evaluate and dereference promises used as function arguments', function() { scope.greet = function(name) { return 'Hi ' + name + '!'; }; scope.name = promise; expect(scope.$eval('greet(name)')).toBe('Hi undefined!'); scope.$digest(); expect(scope.$eval('greet(name)')).toBe('Hi undefined!'); deferred.resolve('Veronica'); expect(scope.$eval('greet(name)')).toBe('Hi undefined!'); scope.$digest(); expect(scope.$eval('greet(name)')).toBe('Hi Veronica!'); }); it('should evaluate and dereference promises used as array indexes', function() { scope.childIndex = promise; scope.kids = ['Adam', 'Veronica', 'Elisa']; expect(scope.$eval('kids[childIndex]')).toBe(undefined); scope.$digest(); expect(scope.$eval('kids[childIndex]')).toBe(undefined); deferred.resolve(1); expect(scope.$eval('kids[childIndex]')).toBe(undefined); scope.$digest(); expect(scope.$eval('kids[childIndex]')).toBe('Veronica'); }); it('should evaluate and dereference promises used as keys in bracket notation', function() { scope.childKey = promise; scope.kids = {'a': 'Adam', 'v': 'Veronica', 'e': 'Elisa'}; expect(scope.$eval('kids[childKey]')).toBe(undefined); scope.$digest(); expect(scope.$eval('kids[childKey]')).toBe(undefined); deferred.resolve('v'); expect(scope.$eval('kids[childKey]')).toBe(undefined); scope.$digest(); expect(scope.$eval('kids[childKey]')).toBe('Veronica'); }); it('should not mess with the promise if it was not directly evaluated', function() { scope.obj = {greeting: promise, username: 'hi'}; var obj = scope.$eval('obj'); expect(obj.username).toEqual('hi'); expect(typeof obj.greeting.then).toBe('function'); }); }); }); }); }); }); angular.js-1.2.11/test/ng/qSpec.js000066400000000000000000001474351227375216300166620ustar00rootroot00000000000000'use strict'; /** http://wiki.commonjs.org/wiki/Promises http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript Q: https://github.com/kriskowal/q https://github.com/kriskowal/q/blob/master/design/README.js https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md http://jsconf.eu/2010/speaker/commonjs_i_promise_by_kris_kow.html - good walkthrough of the Q api's and design, jump to 15:30 twisted: http://twistedmatrix.com/documents/11.0.0/api/twisted.internet.defer.Deferred.html dojo: https://github.com/dojo/dojo/blob/master/_base/Deferred.js http://dojotoolkit.org/api/1.6/dojo/Deferred http://dojotoolkit.org/documentation/tutorials/1.6/promises/ when.js: https://github.com/briancavalier/when.js DART: http://www.dartlang.org/docs/api/Promise.html#Promise::Promise http://code.google.com/p/dart/source/browse/trunk/dart/corelib/src/promise.dart http://codereview.chromium.org/8271014/patch/11003/12005 https://chromereviews.googleplex.com/3365018/ WinJS: http://msdn.microsoft.com/en-us/library/windows/apps/br211867.aspx http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html http://en.wikipedia.org/wiki/Futures_and_promises http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions http://wiki.ecmascript.org/doku.php?id=strawman:async_functions http://jsperf.com/throw-vs-return */ describe('q', function() { var q, defer, deferred, promise, log; // The following private functions are used to help with logging for testing invocation of the // promise callbacks. function _argToString(arg) { return (typeof arg === 'object' && !(arg instanceof Error)) ? toJson(arg) : '' + arg; } function _argumentsToString(args) { return map(sliceArgs(args), _argToString).join(','); } // Help log invocation of success(), finally(), progress() and error() function _logInvocation(funcName, args, returnVal, throwReturnVal) { var logPrefix = funcName + '(' + _argumentsToString(args) + ')'; if (throwReturnVal) { log.push(logPrefix + '->throw(' + _argToString(returnVal) + ')'); throw returnVal; } else { if (returnVal === undefined) { log.push(logPrefix); } else { log.push(logPrefix + '->' + _argToString(returnVal)); } return returnVal; } } /** * Creates a callback that logs its invocation in `log`. * * @param {(number|string)} name Suffix for 'success' name. e.g. success(1) => success1 * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in * value is returned. * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned. */ function success(name, returnVal, throwReturnVal) { var returnValDefined = (arguments.length >= 2); name = 'success' + (name || ''); return function() { return _logInvocation(name, arguments, (returnValDefined ? returnVal : arguments[0]), throwReturnVal); } } /** * Creates a callback that logs its invocation in `log`. * * @param {(number|string)} name Suffix for 'finally' name. e.g. finally(1) => finally * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in * value is returned. * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned. */ function fin(name, returnVal, throwReturnVal) { var returnValDefined = (arguments.length >= 2); name = 'finally' + (name || ''); return function() { return _logInvocation(name, arguments, (returnValDefined ? returnVal : arguments[0]), throwReturnVal); } } /** * Creates a callback that logs its invocation in `log`. * * @param {(number|string)} name Suffix for 'progress' name. e.g. progress(1) => progress * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in * value is returned. * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned. */ function progress(name, returnVal, throwReturnVal) { var returnValDefined = (arguments.length >= 2); name = 'progress' + (name || ''); return function() { return _logInvocation(name, arguments, (returnValDefined ? returnVal : arguments[0]), throwReturnVal); } } /** * Creates a callback that logs its invocation in `log`. * * @param {(number|string)} name Suffix for 'error' name. e.g. error(1) => error * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in * value is first passed to q.reject() and the result is returned. * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned. */ function error(name, returnVal, throwReturnVal) { var returnValDefined = (arguments.length >= 2); name = 'error' + (name || ''); return function() { if (!returnValDefined) { _logInvocation(name, arguments, 'reject(' + _argToString(arguments[0]) + ')', throwReturnVal); return q.reject(arguments[0]); } else { return _logInvocation(name, arguments, returnVal, throwReturnVal); } } } /** helper for synchronous resolution of deferred */ function syncResolve(deferred, result) { deferred.resolve(result); mockNextTick.flush(); } /** helper for synchronous rejection of deferred */ function syncReject(deferred, reason) { deferred.reject(reason); mockNextTick.flush(); } /** helper for synchronous notification of deferred */ function syncNotify(deferred, progress) { deferred.notify(progress); mockNextTick.flush(); } /** converts the `log` to a '; '-separated string */ function logStr() { return log.join('; '); } var mockNextTick = { nextTick: function(task) { mockNextTick.queue.push(task); }, queue: [], logExceptions: true, flush: function() { if (!mockNextTick.queue.length) throw new Error('Nothing to be flushed!'); while (mockNextTick.queue.length) { var queue = mockNextTick.queue; mockNextTick.queue = []; forEach(queue, function(task) { try { task(); } catch(e) { if ( mockNextTick.logExceptions ) { dump('exception in mockNextTick:', e, e.name, e.message, task); } } }); } } } beforeEach(function() { q = qFactory(mockNextTick.nextTick, noop), defer = q.defer; deferred = defer() promise = deferred.promise; log = []; mockNextTick.queue = []; }); afterEach(function() { expect(mockNextTick.queue.length).toBe(0); }); describe('defer', function() { it('should create a new deferred', function() { expect(deferred.promise).toBeDefined(); expect(deferred.resolve).toBeDefined(); expect(deferred.reject).toBeDefined(); }); describe('resolve', function() { it('should fulfill the promise and execute all success callbacks in the registration order', function() { promise.then(success(1), error()); promise.then(success(2), error()); expect(logStr()).toBe(''); deferred.resolve('foo'); mockNextTick.flush(); expect(logStr()).toBe('success1(foo)->foo; success2(foo)->foo'); }); it('should do nothing if a promise was previously resolved', function() { promise.then(success(), error()); expect(logStr()).toBe(''); deferred.resolve('foo'); mockNextTick.flush(); expect(logStr()).toBe('success(foo)->foo'); log = []; deferred.resolve('bar'); deferred.reject('baz'); expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); }); it('should do nothing if a promise was previously rejected', function() { promise.then(success(), error()); expect(logStr()).toBe(''); deferred.reject('foo'); mockNextTick.flush(); expect(logStr()).toBe('error(foo)->reject(foo)'); log = []; deferred.resolve('bar'); deferred.reject('baz'); expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); }); it('should allow deferred resolution with a new promise', function() { var deferred2 = defer(); promise.then(success(), error()); deferred.resolve(deferred2.promise); mockNextTick.flush(); expect(logStr()).toBe(''); deferred2.resolve('foo'); mockNextTick.flush(); expect(logStr()).toBe('success(foo)->foo'); }); it('should call the callback in the next turn', function() { promise.then(success()); expect(logStr()).toBe(''); deferred.resolve('foo'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success(foo)->foo'); }); it('should support non-bound execution', function() { var resolver = deferred.resolve; promise.then(success(), error()); resolver('detached'); mockNextTick.flush(); expect(logStr()).toBe('success(detached)->detached'); }); it('should not break if a callbacks registers another callback', function() { promise.then(function() { log.push('outer'); promise.then(function() { log.push('inner'); }); }); deferred.resolve('foo'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('outer; inner'); }); it('should not break if a callbacks tries to resolve the deferred again', function() { promise.then(function(val) { log.push('then1(' + val + ')->resolve(bar)'); deferred.resolve('bar'); // nop }); promise.then(success(2)); deferred.resolve('foo'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('then1(foo)->resolve(bar); success2(foo)->foo'); }); }); describe('reject', function() { it('should reject the promise and execute all error callbacks in the registration order', function() { promise.then(success(), error(1)); promise.then(success(), error(2)); expect(logStr()).toBe(''); deferred.reject('foo'); mockNextTick.flush(); expect(logStr()).toBe('error1(foo)->reject(foo); error2(foo)->reject(foo)'); }); it('should do nothing if a promise was previously resolved', function() { promise.then(success(1), error(1)); expect(logStr()).toBe(''); deferred.resolve('foo'); mockNextTick.flush(); expect(logStr()).toBe('success1(foo)->foo'); log = []; deferred.reject('bar'); deferred.resolve('baz'); expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); promise.then(success(2), error(2)) expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success2(foo)->foo'); }); it('should do nothing if a promise was previously rejected', function() { promise.then(success(1), error(1)); expect(logStr()).toBe(''); deferred.reject('foo'); mockNextTick.flush(); expect(logStr()).toBe('error1(foo)->reject(foo)'); log = []; deferred.reject('bar'); deferred.resolve('baz'); expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); promise.then(success(2), error(2)) expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error2(foo)->reject(foo)'); }); it('should not defer rejection with a new promise', function() { var deferred2 = defer(); promise.then(success(), error()); deferred.reject(deferred2.promise); mockNextTick.flush(); expect(logStr()).toBe('error({})->reject({})'); }); it('should call the error callback in the next turn', function() { promise.then(success(), error()); expect(logStr()).toBe(''); deferred.reject('foo'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error(foo)->reject(foo)'); }); it('should support non-bound execution', function() { var rejector = deferred.reject; promise.then(success(), error()); rejector('detached'); mockNextTick.flush(); expect(logStr()).toBe('error(detached)->reject(detached)'); }); }); describe('notify', function() { it('should execute all progress callbacks in the registration order', function() { promise.then(success(1), error(1), progress(1)); promise.then(success(2), error(2), progress(2)); expect(logStr()).toBe(''); deferred.notify('foo'); mockNextTick.flush(); expect(logStr()).toBe('progress1(foo)->foo; progress2(foo)->foo'); }); it('should do nothing if a promise was previously resolved', function() { promise.then(success(1), error(1), progress(1)); expect(logStr()).toBe(''); deferred.resolve('foo'); mockNextTick.flush(); expect(logStr()).toBe('success1(foo)->foo'); log = []; deferred.notify('bar'); expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); }); it('should do nothing if a promise was previously rejected', function() { promise.then(success(1), error(1), progress(1)); expect(logStr()).toBe(''); deferred.reject('foo'); mockNextTick.flush(); expect(logStr()).toBe('error1(foo)->reject(foo)'); log = []; deferred.reject('bar'); deferred.resolve('baz'); deferred.notify('qux') expect(mockNextTick.queue.length).toBe(0); expect(logStr()).toBe(''); promise.then(success(2), error(2), progress(2)); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error2(foo)->reject(foo)'); }); it('should not apply any special treatment to promises passed to notify', function() { var deferred2 = defer(); promise.then(success(), error(), progress()); deferred.notify(deferred2.promise); mockNextTick.flush(); expect(logStr()).toBe('progress({})->{}'); }); it('should call the progress callbacks in the next turn', function() { promise.then(success(), error(), progress(1)); promise.then(success(), error(), progress(2)); expect(logStr()).toBe(''); deferred.notify('foo'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('progress1(foo)->foo; progress2(foo)->foo'); }); it('should ignore notifications sent out in the same turn before listener registration', function() { deferred.notify('foo'); promise.then(success(), error(), progress(1)); expect(logStr()).toBe(''); expect(mockNextTick.queue).toEqual([]); }); it('should support non-bound execution', function() { var notify = deferred.notify; promise.then(success(), error(), progress()); notify('detached'); mockNextTick.flush(); expect(logStr()).toBe('progress(detached)->detached'); }); it("should not save and re-emit progress notifications between ticks", function () { promise.then(success(1), error(1), progress(1)); deferred.notify('foo'); deferred.notify('bar'); mockNextTick.flush(); expect(logStr()).toBe('progress1(foo)->foo; progress1(bar)->bar'); log = []; promise.then(success(2), error(2), progress(2)); deferred.notify('baz'); mockNextTick.flush(); expect(logStr()).toBe('progress1(baz)->baz; progress2(baz)->baz'); }); }); describe('promise', function() { it('should have a then method', function() { expect(typeof promise.then).toBe('function'); }); it('should have a catch method', function() { expect(typeof promise['catch']).toBe('function'); }); it('should have a finally method', function() { expect(typeof promise['finally']).toBe('function'); }); describe('then', function() { it('should allow registration of a success callback without an errback or progressback ' + 'and resolve', function() { promise.then(success()); syncResolve(deferred, 'foo'); expect(logStr()).toBe('success(foo)->foo'); }); it('should allow registration of a success callback without an errback and reject', function() { promise.then(success()); syncReject(deferred, 'foo'); expect(logStr()).toBe(''); }); it('should allow registration of a success callback without an progressback and notify', function() { promise.then(success()); syncNotify(deferred, 'doing'); expect(logStr()).toBe(''); }); it('should allow registration of an errback without a success or progress callback and ' + ' reject', function() { promise.then(null, error()); syncReject(deferred, 'oops!'); expect(logStr()).toBe('error(oops!)->reject(oops!)'); }); it('should allow registration of an errback without a success callback and resolve', function() { promise.then(null, error()); syncResolve(deferred, 'done'); expect(logStr()).toBe(''); }); it('should allow registration of an errback without a progress callback and notify', function() { promise.then(null, error()); syncNotify(deferred, 'doing'); expect(logStr()).toBe(''); }); it('should allow registration of an progressback without a success or error callback and ' + 'notify', function() { promise.then(null, null, progress()); syncNotify(deferred, 'doing'); expect(logStr()).toBe('progress(doing)->doing'); }); it('should allow registration of an progressback without a success callback and resolve', function() { promise.then(null, null, progress()); syncResolve(deferred, 'done'); expect(logStr()).toBe(''); }); it('should allow registration of an progressback without a error callback and reject', function() { promise.then(null, null, progress()); syncReject(deferred, 'oops!'); expect(logStr()).toBe(''); }); it('should resolve all callbacks with the original value', function() { promise.then(success('A', 'aVal'), error(), progress()); promise.then(success('B', 'bErr', true), error(), progress()); promise.then(success('C', q.reject('cReason')), error(), progress()); promise.then(success('D', q.reject('dReason'), true), error(), progress()); promise.then(success('E', 'eVal'), error(), progress()); expect(logStr()).toBe(''); syncResolve(deferred, 'yup'); expect(log).toEqual(['successA(yup)->aVal', 'successB(yup)->throw(bErr)', 'successC(yup)->{}', 'successD(yup)->throw({})', 'successE(yup)->eVal']); }); it('should reject all callbacks with the original reason', function() { promise.then(success(), error('A', 'aVal'), progress()); promise.then(success(), error('B', 'bEr', true), progress()); promise.then(success(), error('C', q.reject('cReason')), progress()); promise.then(success(), error('D', 'dVal'), progress()); expect(logStr()).toBe(''); syncReject(deferred, 'noo!'); expect(logStr()).toBe('errorA(noo!)->aVal; errorB(noo!)->throw(bEr); errorC(noo!)->{}; errorD(noo!)->dVal'); }); it('should notify all callbacks with the original value', function() { promise.then(success(), error(), progress('A', 'aVal')); promise.then(success(), error(), progress('B', 'bErr', true)); promise.then(success(), error(), progress('C', q.reject('cReason'))); promise.then(success(), error(), progress('C_reject', q.reject('cRejectReason'), true)); promise.then(success(), error(), progress('Z', 'the end!')); expect(logStr()).toBe(''); syncNotify(deferred, 'yup'); expect(log).toEqual(['progressA(yup)->aVal', 'progressB(yup)->throw(bErr)', 'progressC(yup)->{}', 'progressC_reject(yup)->throw({})', 'progressZ(yup)->the end!']); }); it('should propagate resolution and rejection between dependent promises', function() { promise.then(success(1, 'x'), error('1')). then(success(2, 'y', true), error('2')). then(success(3), error(3, 'z', true)). then(success(4), error(4, 'done')). then(success(5), error(5)); expect(logStr()).toBe(''); syncResolve(deferred, 'sweet!'); expect(log).toEqual(['success1(sweet!)->x', 'success2(x)->throw(y)', 'error3(y)->throw(z)', 'error4(z)->done', 'success5(done)->done']); }); it('should propagate notification between dependent promises', function() { promise.then(success(), error(), progress(1, 'a')). then(success(), error(), progress(2, 'b')). then(success(), error(), progress(3, 'c')). then(success(), error(), progress(4)). then(success(), error(), progress(5)); expect(logStr()).toBe(''); syncNotify(deferred, 'wait'); expect(log).toEqual(['progress1(wait)->a', 'progress2(a)->b', 'progress3(b)->c', 'progress4(c)->c', 'progress5(c)->c']); }); it('should reject a derived promise if an exception is thrown while resolving its parent', function() { promise.then(success(1, 'oops', true), error(1)). then(success(2), error(2)); syncResolve(deferred, 'done!'); expect(logStr()).toBe('success1(done!)->throw(oops); error2(oops)->reject(oops)'); }); it('should reject a derived promise if an exception is thrown while rejecting its parent', function() { promise.then(null, error(1, 'oops', true)). then(success(2), error(2)); syncReject(deferred, 'timeout'); expect(logStr()).toBe('error1(timeout)->throw(oops); error2(oops)->reject(oops)'); }); it('should stop notification propagation in case of error', function() { promise.then(success(), error(), progress(1)). then(success(), error(), progress(2, 'ops!', true)). then(success(), error(), progress(3)); expect(logStr()).toBe(''); syncNotify(deferred, 'wait'); expect(log).toEqual(['progress1(wait)->wait', 'progress2(wait)->throw(ops!)']); }); it('should call success callback in the next turn even if promise is already resolved', function() { deferred.resolve('done!'); promise.then(success()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(log).toEqual(['success(done!)->done!']); }); it('should call error callback in the next turn even if promise is already rejected', function() { deferred.reject('oops!'); promise.then(null, error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(log).toEqual(['error(oops!)->reject(oops!)']); }); it('should forward success resolution when success callbacks are not functions', function() { deferred.resolve('yay!'); promise.then(1). then(null). then({}). then('gah!'). then([]). then(success()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(log).toEqual(['success(yay!)->yay!']); }); it('should forward error resolution when error callbacks are not functions', function() { deferred.reject('oops!'); promise.then(null, 1). then(null, null). then(null, {}). then(null, 'gah!'). then(null, []). then(null, error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(log).toEqual(['error(oops!)->reject(oops!)']); }); }); describe('finally', function() { it('should not take an argument', function() { promise['finally'](fin(1)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()'); }); describe("when the promise is fulfilled", function () { it('should call the callback', function() { promise.then(success(1))['finally'](fin(1)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('success1(foo)->foo; finally1()'); }); it('should fulfill with the original value', function() { promise['finally'](fin('B', 'b'), error('B')). then(success('BB', 'bb'), error('BB')); syncResolve(deferred, 'RESOLVED_VAL'); expect(log).toEqual(['finallyB()->b', 'successBB(RESOLVED_VAL)->bb']); }); it('should fulfill with the original value (larger test)', function() { promise.then(success('A', 'a'), error('A')); promise['finally'](fin('B', 'b'), error('B')). then(success('BB', 'bb'), error('BB')); promise.then(success('C', 'c'), error('C'))['finally'](fin('CC', 'IGNORED')) .then(success('CCC', 'cc'), error('CCC')) .then(success('CCCC', 'ccc'), error('CCCC')) syncResolve(deferred, 'RESOLVED_VAL'); expect(log).toEqual(['successA(RESOLVED_VAL)->a', 'finallyB()->b', 'successC(RESOLVED_VAL)->c', 'successBB(RESOLVED_VAL)->bb', 'finallyCC()->IGNORED', 'successCCC(c)->cc', 'successCCCC(cc)->ccc']); }); describe("when the callback returns a promise", function() { describe("that is fulfilled", function() { it("should fulfill with the original reason after that promise resolves", function () { var returnedDef = defer(); returnedDef.resolve('bar'); promise['finally'](fin(1, returnedDef.promise)) .then(success(2)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()->{}; success2(foo)->foo'); }); }); describe("that is rejected", function() { it("should reject with this new rejection reason", function () { var returnedDef = defer() returnedDef.reject('bar'); promise['finally'](fin(1, returnedDef.promise)) .then(success(2), error(1)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()->{}; error1(bar)->reject(bar)'); }); }); }); describe("when the callback throws an exception", function() { it("should reject with this new exception", function() { promise['finally'](fin(1, "exception", true)) .then(success(1), error(2)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()->throw(exception); error2(exception)->reject(exception)'); }); }); }); describe("when the promise is rejected", function () { it("should call the callback", function () { promise['finally'](fin(1)) .then(success(2), error(1)) syncReject(deferred, 'foo'); expect(logStr()).toBe('finally1(); error1(foo)->reject(foo)'); }); it('should reject with the original reason', function() { promise['finally'](fin(1), "hello") .then(success(2), error(2)) syncReject(deferred, 'original'); expect(logStr()).toBe('finally1(); error2(original)->reject(original)'); }); describe("when the callback returns a promise", function() { describe("that is fulfilled", function() { it("should reject with the original reason after that promise resolves", function () { var returnedDef = defer() returnedDef.resolve('bar'); promise['finally'](fin(1, returnedDef.promise)) .then(success(2), error(2)) syncReject(deferred, 'original'); expect(logStr()).toBe('finally1()->{}; error2(original)->reject(original)'); }); }); describe("that is rejected", function () { it("should reject with the new reason", function() { var returnedDef = defer() returnedDef.reject('bar'); promise['finally'](fin(1, returnedDef.promise)) .then(success(2), error(1)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()->{}; error1(bar)->reject(bar)'); }); }); }); describe("when the callback throws an exception", function() { it("should reject with this new exception", function() { promise['finally'](fin(1, "exception", true)) .then(success(1), error(2)) syncResolve(deferred, 'foo'); expect(logStr()).toBe('finally1()->throw(exception); error2(exception)->reject(exception)'); }); }); }); }); describe('catch', function() { it('should be a shorthand for defining promise error handlers', function() { promise['catch'](error(1)).then(null, error(2)) syncReject(deferred, 'foo'); expect(logStr()).toBe('error1(foo)->reject(foo); error2(foo)->reject(foo)'); }); }); }); }); describe('reject', function() { it('should package a string into a rejected promise', function() { var rejectedPromise = q.reject('not gonna happen'); promise.then(success(), error()); syncResolve(deferred, rejectedPromise); expect(log).toEqual(['error(not gonna happen)->reject(not gonna happen)']); }); it('should package an exception into a rejected promise', function() { var rejectedPromise = q.reject(Error('not gonna happen')); promise.then(success(), error()); syncResolve(deferred, rejectedPromise); expect(log).toEqual(['error(Error: not gonna happen)->reject(Error: not gonna happen)']); }); it('should return a promise that forwards callbacks if the callbacks are missing', function() { var rejectedPromise = q.reject('rejected'); promise.then(success(), error()); syncResolve(deferred, rejectedPromise.then()); expect(log).toEqual(['error(rejected)->reject(rejected)']); }); it('should catch exceptions thrown in errback and forward them to derived promises', function() { var rejectedPromise = q.reject('rejected'); rejectedPromise.then(null, error('Broken', 'catch me!', true)). then(null, error('Affected')) mockNextTick.flush(); expect(log).toEqual(['errorBroken(rejected)->throw(catch me!)', 'errorAffected(catch me!)->reject(catch me!)']); }); it('should have functions `finally` and `catch`', function() { var rejectedPromise = q.reject('rejected'); expect(rejectedPromise['finally']).not.toBeUndefined(); expect(rejectedPromise['catch']).not.toBeUndefined(); }); }); describe('when', function() { describe('resolution', function() { it('should call the success callback in the next turn when the value is a non-promise', function() { q.when('hello', success(), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success(hello)->hello'); }); it('should call the success callback in the next turn when the value is a resolved promise', function() { deferred.resolve('hello'); q.when(deferred.promise, success(), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success(hello)->hello'); }); it('should call the errback in the next turn when the value is a rejected promise', function() { deferred.reject('nope'); q.when(deferred.promise, success(), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error(nope)->reject(nope)'); }); it('should call the success callback after the original promise is resolved', function() { q.when(deferred.promise, success(), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe(''); syncResolve(deferred, 'hello'); expect(logStr()).toBe('success(hello)->hello'); }); it('should call the errback after the orignal promise is rejected', function() { q.when(deferred.promise, success(), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe(''); syncReject(deferred, 'nope'); expect(logStr()).toBe('error(nope)->reject(nope)'); }); }); describe('notification', function() { it('should call the progressback when the value is a promise and gets notified', function() { q.when(deferred.promise, success(), error(), progress()); mockNextTick.flush(); expect(logStr()).toBe(''); syncNotify(deferred, 'notification'); expect(logStr()).toBe('progress(notification)->notification'); }); }); describe('optional callbacks', function() { it('should not require success callback and propagate resolution', function() { q.when('hi', null, error()).then(success(2), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success2(hi)->hi'); }); it('should not require success callback and propagate rejection', function() { q.when(q.reject('sorry'), null, error(1)).then(success(), error(2)); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->reject(sorry); error2(sorry)->reject(sorry)'); }); it('should not require errback and propagate resolution', function() { q.when('hi', success(1, 'hello')).then(success(2), error()); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('success1(hi)->hello; success2(hello)->hello'); }); it('should not require errback and propagate rejection', function() { q.when(q.reject('sorry'), success()).then(success(2), error(2)); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe('error2(sorry)->reject(sorry)'); }); it('should not require progressback and propagate notification', function() { q.when(deferred.promise). then(success(), error(), progress()); mockNextTick.flush(); expect(logStr()).toBe(''); syncNotify(deferred, 'notification'); expect(logStr()).toBe('progress(notification)->notification'); }); }); describe('returned promise', function() { it('should return a promise that can be resolved with a value returned from the success ' + 'callback', function() { q.when('hello', success(1, 'hi'), error()).then(success(2), error()); mockNextTick.flush(); expect(logStr()).toBe('success1(hello)->hi; success2(hi)->hi'); }); it('should return a promise that can be rejected with a rejected promise returned from the ' + 'success callback', function() { q.when('hello', success(1, q.reject('sorry')), error()).then(success(), error(2)); mockNextTick.flush(); expect(logStr()).toBe('success1(hello)->{}; error2(sorry)->reject(sorry)'); }); it('should return a promise that can be resolved with a value returned from the errback', function() { q.when(q.reject('sorry'), success(), error(1, 'hi')).then(success(2), error()); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->hi; success2(hi)->hi'); }); it('should return a promise that can be rejected with a rejected promise returned from the ' + 'errback', function() { q.when(q.reject('sorry'), success(), error(1, q.reject('sigh'))).then(success(), error(2)); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->{}; error2(sigh)->reject(sigh)'); }); it('should return a promise that can be resolved with a promise returned from the success ' + 'callback', function() { var deferred2 = defer(); q.when('hi', success(1, deferred2.promise), error()).then(success(2), error()); mockNextTick.flush(); expect(logStr()).toBe('success1(hi)->{}'); syncResolve(deferred2, 'finally!'); expect(logStr()).toBe('success1(hi)->{}; success2(finally!)->finally!'); }); it('should return a promise that can be resolved with promise returned from the errback ' + 'callback', function() { var deferred2 = defer(); q.when(q.reject('sorry'), success(), error(1, deferred2.promise)).then(success(2), error()); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->{}'); syncResolve(deferred2, 'finally!'); expect(logStr()).toBe('error1(sorry)->{}; success2(finally!)->finally!'); }); }); describe('security', function() { it('should call success callback only once even if the original promise gets fullfilled ' + 'multiple times', function() { var evilPromise = { then: function(success, error, progress) { evilPromise.success = success; evilPromise.error = error; evilPromise.progress = progress; } }; q.when(evilPromise, success(), error()); mockNextTick.flush(); expect(logStr()).toBe(''); evilPromise.success('done'); mockNextTick.flush(); // TODO(i) wrong queue, evil promise would be resolved outside of the // scope.$apply lifecycle and in that case we should have some kind // of fallback queue for calling our callbacks from. Otherwise the // application will get stuck until something triggers next $apply. expect(logStr()).toBe('success(done)->done'); evilPromise.success('evil is me'); evilPromise.error('burn burn'); expect(logStr()).toBe('success(done)->done'); }); it('should call errback only once even if the original promise gets fullfilled multiple ' + 'times', function() { var evilPromise = { then: function(success, error, progress) { evilPromise.success = success; evilPromise.error = error; evilPromise.progress = progress; } }; q.when(evilPromise, success(), error()); mockNextTick.flush(); expect(logStr()).toBe(''); evilPromise.error('failed'); expect(logStr()).toBe('error(failed)->reject(failed)'); evilPromise.error('muhaha'); evilPromise.success('take this'); expect(logStr()).toBe('error(failed)->reject(failed)'); }); it('should not call progressback after promise gets fullfilled, even if original promise ' + 'gets notified multiple times', function() { var evilPromise = { then: function(success, error, progress) { evilPromise.success = success; evilPromise.error = error; evilPromise.progress = progress; } }; q.when(evilPromise, success(), error(), progress()); mockNextTick.flush(); expect(logStr()).toBe(''); evilPromise.progress('notification'); evilPromise.success('ok'); mockNextTick.flush(); expect(logStr()).toBe('progress(notification)->notification; success(ok)->ok'); evilPromise.progress('muhaha'); expect(logStr()).toBe('progress(notification)->notification; success(ok)->ok'); }); }); }); describe('all (array)', function() { it('should resolve all or nothing', function() { var result; q.all([]).then(function(r) { result = r; }); mockNextTick.flush(); expect(result).toEqual([]); }); it('should take an array of promises and return a promise for an array of results', function() { var deferred1 = defer(), deferred2 = defer(); q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error()); expect(logStr()).toBe(''); syncResolve(deferred, 'hi'); expect(logStr()).toBe(''); syncResolve(deferred2, 'cau'); expect(logStr()).toBe(''); syncResolve(deferred1, 'hola'); expect(logStr()).toBe('success(["hi","hola","cau"])->["hi","hola","cau"]'); }); it('should reject the derived promise if at least one of the promises in the array is rejected', function() { var deferred1 = defer(), deferred2 = defer(); q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error()); expect(logStr()).toBe(''); syncResolve(deferred2, 'cau'); expect(logStr()).toBe(''); syncReject(deferred1, 'oops'); expect(logStr()).toBe('error(oops)->reject(oops)'); }); it('should not forward notifications from individual promises to the combined promise', function() { var deferred1 = defer(), deferred2 = defer(); q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error(), progress()); expect(logStr()).toBe(''); deferred.notify('x'); deferred2.notify('y'); expect(logStr()).toBe(''); mockNextTick.flush(); expect(logStr()).toBe(''); }); it('should ignore multiple resolutions of an (evil) array promise', function() { var evilPromise = { then: function(success, error) { evilPromise.success = success; evilPromise.error = error; } }; q.all([promise, evilPromise]).then(success(), error()); expect(logStr()).toBe(''); evilPromise.success('first'); evilPromise.success('muhaha'); evilPromise.error('arghhh'); expect(logStr()).toBe(''); syncResolve(deferred, 'done'); expect(logStr()).toBe('success(["done","first"])->["done","first"]'); }); }); describe('all (hash)', function() { it('should resolve all or nothing', function() { var result; q.all({}).then(function(r) { result = r; }); mockNextTick.flush(); expect(result).toEqual({}); }); it('should take a hash of promises and return a promise for a hash of results', function() { var deferred1 = defer(), deferred2 = defer(); q.all({en: promise, fr: deferred1.promise, es: deferred2.promise}).then(success(), error()); expect(logStr()).toBe(''); syncResolve(deferred, 'hi'); expect(logStr()).toBe(''); syncResolve(deferred2, 'hola'); expect(logStr()).toBe(''); syncResolve(deferred1, 'salut'); expect(logStr()).toBe('success({"en":"hi","es":"hola","fr":"salut"})->{"en":"hi","es":"hola","fr":"salut"}'); }); it('should reject the derived promise if at least one of the promises in the hash is rejected', function() { var deferred1 = defer(), deferred2 = defer(); q.all({en: promise, fr: deferred1.promise, es: deferred2.promise}).then(success(), error()); expect(logStr()).toBe(''); syncResolve(deferred2, 'hola'); expect(logStr()).toBe(''); syncReject(deferred1, 'oops'); expect(logStr()).toBe('error(oops)->reject(oops)'); }); it('should ignore multiple resolutions of an (evil) hash promise', function() { var evilPromise = { then: function(success, error) { evilPromise.success = success; evilPromise.error = error; } }; q.all({good: promise, evil: evilPromise}).then(success(), error()); expect(logStr()).toBe(''); evilPromise.success('first'); evilPromise.success('muhaha'); evilPromise.error('arghhh'); expect(logStr()).toBe(''); syncResolve(deferred, 'done'); expect(logStr()).toBe('success({"evil":"first","good":"done"})->{"evil":"first","good":"done"}'); }); it('should handle correctly situation when given the same promise several times', function() { q.all({first: promise, second: promise, third: promise}).then(success(), error()); expect(logStr()).toBe(''); syncResolve(deferred, 'done'); expect(logStr()).toBe('success({"first":"done","second":"done","third":"done"})->{"first":"done","second":"done","third":"done"}'); }); }); describe('exception logging', function() { var mockExceptionLogger = { log: [], logger: function(e) { mockExceptionLogger.log.push(e); } } beforeEach(function() { q = qFactory(mockNextTick.nextTick, mockExceptionLogger.logger), defer = q.defer; deferred = defer() promise = deferred.promise; log = []; mockExceptionLogger.log = []; }); describe('in then', function() { it('should log exceptions thrown in a success callback and reject the derived promise', function() { var success1 = success(1, 'oops', true); promise.then(success1).then(success(2), error(2)); syncResolve(deferred, 'done'); expect(logStr()).toBe('success1(done)->throw(oops); error2(oops)->reject(oops)'); expect(mockExceptionLogger.log).toEqual(['oops']); }); it('should NOT log exceptions when a success callback returns rejected promise', function() { promise.then(success(1, q.reject('rejected'))).then(success(2), error(2)); syncResolve(deferred, 'done'); expect(logStr()).toBe('success1(done)->{}; error2(rejected)->reject(rejected)'); expect(mockExceptionLogger.log).toEqual([]); }); it('should log exceptions thrown in a errback and reject the derived promise', function() { var error1 = error(1, 'oops', true); promise.then(null, error1).then(success(2), error(2)); syncReject(deferred, 'nope'); expect(logStr()).toBe('error1(nope)->throw(oops); error2(oops)->reject(oops)'); expect(mockExceptionLogger.log).toEqual(['oops']); }); it('should NOT log exceptions when an errback returns a rejected promise', function() { promise.then(null, error(1, q.reject('rejected'))).then(success(2), error(2)); syncReject(deferred, 'nope'); expect(logStr()).toBe('error1(nope)->{}; error2(rejected)->reject(rejected)'); expect(mockExceptionLogger.log).toEqual([]); }); it('should log exceptions throw in a progressack and stop propagation, but shoud NOT reject ' + 'the promise', function() { promise.then(success(), error(), progress(1, 'failed', true)).then(null, error(1), progress(2)); syncNotify(deferred, '10%'); expect(logStr()).toBe('progress1(10%)->throw(failed)'); expect(mockExceptionLogger.log).toEqual(['failed']); log = []; syncResolve(deferred, 'ok'); expect(logStr()).toBe('success(ok)->ok'); }); }); describe('in when', function() { it('should log exceptions thrown in a success callback and reject the derived promise', function() { var success1 = success(1, 'oops', true); q.when('hi', success1, error()).then(success(), error(2)); mockNextTick.flush(); expect(logStr()).toBe('success1(hi)->throw(oops); error2(oops)->reject(oops)'); expect(mockExceptionLogger.log).toEqual(['oops']); }); it('should NOT log exceptions when a success callback returns rejected promise', function() { q.when('hi', success(1, q.reject('rejected'))).then(success(2), error(2)); mockNextTick.flush(); expect(logStr()).toBe('success1(hi)->{}; error2(rejected)->reject(rejected)'); expect(mockExceptionLogger.log).toEqual([]); }); it('should log exceptions thrown in a errback and reject the derived promise', function() { var error1 = error(1, 'oops', true); q.when(q.reject('sorry'), success(), error1).then(success(), error(2)); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->throw(oops); error2(oops)->reject(oops)'); expect(mockExceptionLogger.log).toEqual(['oops']); }); it('should NOT log exceptions when an errback returns a rejected promise', function() { q.when(q.reject('sorry'), success(), error(1, q.reject('rejected'))). then(success(2), error(2)); mockNextTick.flush(); expect(logStr()).toBe('error1(sorry)->{}; error2(rejected)->reject(rejected)'); expect(mockExceptionLogger.log).toEqual([]); }); }); }); describe('when exceptionHandler rethrows exceptions, ', function() { var originalLogExceptions, deferred, errorSpy, exceptionExceptionSpy; beforeEach(function() { // Turn off exception logging for these particular tests originalLogExceptions = mockNextTick.logExceptions; mockNextTick.logExceptions = false; // Set up spies exceptionExceptionSpy = jasmine.createSpy('rethrowExceptionHandler') .andCallFake(function rethrowExceptionHandler(e) { throw e; }); errorSpy = jasmine.createSpy('errorSpy'); q = qFactory(mockNextTick.nextTick, exceptionExceptionSpy); deferred = q.defer(); }); afterEach(function() { // Restore the original exception logging mode mockNextTick.logExceptions = originalLogExceptions; }); it('should still reject the promise, when exception is thrown in success handler, even if exceptionHandler rethrows', function() { deferred.promise.then(function() { throw 'reject'; }).then(null, errorSpy); deferred.resolve('resolve'); mockNextTick.flush(); expect(exceptionExceptionSpy).toHaveBeenCalled(); expect(errorSpy).toHaveBeenCalled(); }); it('should still reject the promise, when exception is thrown in error handler, even if exceptionHandler rethrows', function() { deferred.promise.then(null, function() { throw 'reject again'; }).then(null, errorSpy); deferred.reject('reject'); mockNextTick.flush(); expect(exceptionExceptionSpy).toHaveBeenCalled(); expect(errorSpy).toHaveBeenCalled(); }); }); }); angular.js-1.2.11/test/ng/rootElementSpec.js000066400000000000000000000004701227375216300207020ustar00rootroot00000000000000'use strict'; describe('$rootElement', function() { it('should publish the bootstrap element into $rootElement', function() { var element = jqLite('
                      '); var injector = angular.bootstrap(element); expect(injector.get('$rootElement')[0]).toBe(element[0]); dealoc(element); }); }); angular.js-1.2.11/test/ng/rootScopeSpec.js000066400000000000000000001436021227375216300203670ustar00rootroot00000000000000'use strict'; describe('Scope', function() { beforeEach(module(provideLog)); describe('$root', function() { it('should point to itself', inject(function($rootScope) { expect($rootScope.$root).toEqual($rootScope); expect($rootScope.hasOwnProperty('$root')).toBeTruthy(); })); it('should expose the constructor', inject(function($rootScope) { if (msie) return; expect($rootScope.__proto__).toBe($rootScope.constructor.prototype); })); it('should not have $root on children, but should inherit', inject(function($rootScope) { var child = $rootScope.$new(); expect(child.$root).toEqual($rootScope); expect(child.hasOwnProperty('$root')).toBeFalsy(); })); }); describe('$parent', function() { it('should point to itself in root', inject(function($rootScope) { expect($rootScope.$root).toEqual($rootScope); })); it('should point to parent', inject(function($rootScope) { var child = $rootScope.$new(); expect($rootScope.$parent).toEqual(null); expect(child.$parent).toEqual($rootScope); expect(child.$new().$parent).toEqual(child); })); }); describe('$id', function() { it('should have a unique id', inject(function($rootScope) { expect($rootScope.$id < $rootScope.$new().$id).toBeTruthy(); })); }); describe('this', function() { it('should have a \'this\'', inject(function($rootScope) { expect($rootScope['this']).toEqual($rootScope); })); }); describe('$new()', function() { it('should create a child scope', inject(function($rootScope) { var child = $rootScope.$new(); $rootScope.a = 123; expect(child.a).toEqual(123); })); it('should create a non prototypically inherited child scope', inject(function($rootScope) { var child = $rootScope.$new(true); $rootScope.a = 123; expect(child.a).toBeUndefined(); expect(child.$parent).toEqual($rootScope); expect(child.$new).toBe($rootScope.$new); expect(child.$root).toBe($rootScope); })); }); describe('$watch/$digest', function() { it('should watch and fire on simple property change', inject(function($rootScope) { var spy = jasmine.createSpy(); $rootScope.$watch('name', spy); $rootScope.$digest(); spy.reset(); expect(spy).not.wasCalled(); $rootScope.$digest(); expect(spy).not.wasCalled(); $rootScope.name = 'misko'; $rootScope.$digest(); expect(spy).wasCalledWith('misko', undefined, $rootScope); })); it('should watch and fire on expression change', inject(function($rootScope) { var spy = jasmine.createSpy(); $rootScope.$watch('name.first', spy); $rootScope.$digest(); spy.reset(); $rootScope.name = {}; expect(spy).not.wasCalled(); $rootScope.$digest(); expect(spy).not.wasCalled(); $rootScope.name.first = 'misko'; $rootScope.$digest(); expect(spy).wasCalled(); })); it('should not keep constant expressions on watch queue', inject(function($rootScope) { $rootScope.$watch('1 + 1', function() {}); expect($rootScope.$$watchers.length).toEqual(1); $rootScope.$digest(); expect($rootScope.$$watchers.length).toEqual(0); })); it('should delegate exceptions', function() { module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); }); inject(function($rootScope, $exceptionHandler, $log) { $rootScope.$watch('a', function() {throw new Error('abc');}); $rootScope.a = 1; $rootScope.$digest(); expect($exceptionHandler.errors[0].message).toEqual('abc'); $log.assertEmpty(); }); }); it('should clear phase if an exception interrupt $digest cycle', function() { inject(function($rootScope) { $rootScope.$watch('a', function() {throw new Error('abc');}); $rootScope.a = 1; try { $rootScope.$digest(); } catch(e) { } expect($rootScope.$$phase).toBeNull(); }); }); it('should fire watches in order of addition', inject(function($rootScope) { // this is not an external guarantee, just our own sanity var log = ''; $rootScope.$watch('a', function() { log += 'a'; }); $rootScope.$watch('b', function() { log += 'b'; }); // constant expressions have slightly different handling, // let's ensure they are kept in the same list as others $rootScope.$watch('1', function() { log += '1'; }); $rootScope.$watch('c', function() { log += 'c'; }); $rootScope.$watch('2', function() { log += '2'; }); $rootScope.a = $rootScope.b = $rootScope.c = 1; $rootScope.$digest(); expect(log).toEqual('ab1c2'); })); it('should call child $watchers in addition order', inject(function($rootScope) { // this is not an external guarantee, just our own sanity var log = ''; var childA = $rootScope.$new(); var childB = $rootScope.$new(); var childC = $rootScope.$new(); childA.$watch('a', function() { log += 'a'; }); childB.$watch('b', function() { log += 'b'; }); childC.$watch('c', function() { log += 'c'; }); childA.a = childB.b = childC.c = 1; $rootScope.$digest(); expect(log).toEqual('abc'); })); it('should allow $digest on a child scope with and without a right sibling', inject( function($rootScope) { // tests a traversal edge case which we originally missed var log = '', childA = $rootScope.$new(), childB = $rootScope.$new(); $rootScope.$watch(function() { log += 'r'; }); childA.$watch(function() { log += 'a'; }); childB.$watch(function() { log += 'b'; }); // init $rootScope.$digest(); expect(log).toBe('rabrab'); log = ''; childA.$digest(); expect(log).toBe('a'); log = ''; childB.$digest(); expect(log).toBe('b'); })); it('should repeat watch cycle while model changes are identified', inject(function($rootScope) { var log = ''; $rootScope.$watch('c', function(v) {$rootScope.d = v; log+='c'; }); $rootScope.$watch('b', function(v) {$rootScope.c = v; log+='b'; }); $rootScope.$watch('a', function(v) {$rootScope.b = v; log+='a'; }); $rootScope.$digest(); log = ''; $rootScope.a = 1; $rootScope.$digest(); expect($rootScope.b).toEqual(1); expect($rootScope.c).toEqual(1); expect($rootScope.d).toEqual(1); expect(log).toEqual('abc'); })); it('should repeat watch cycle from the root element', inject(function($rootScope) { var log = ''; var child = $rootScope.$new(); $rootScope.$watch(function() { log += 'a'; }); child.$watch(function() { log += 'b'; }); $rootScope.$digest(); expect(log).toEqual('abab'); })); it('should prevent infinite recursion and print watcher expression',function() { module(function($rootScopeProvider) { $rootScopeProvider.digestTtl(100); }); inject(function($rootScope) { $rootScope.$watch('a', function() {$rootScope.b++;}); $rootScope.$watch('b', function() {$rootScope.a++;}); $rootScope.a = $rootScope.b = 0; expect(function() { $rootScope.$digest(); }).toThrowMinErr('$rootScope', 'infdig', '100 $digest() iterations reached. Aborting!\n'+ 'Watchers fired in the last 5 iterations: ' + '[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' + '["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' + '["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' + '["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' + '["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]'); expect($rootScope.$$phase).toBeNull(); }); }); it('should prevent infinite recursion and print watcher function name or body', inject(function($rootScope) { $rootScope.$watch(function watcherA() {return $rootScope.a;}, function() {$rootScope.b++;}); $rootScope.$watch(function() {return $rootScope.b;}, function() {$rootScope.a++;}); $rootScope.a = $rootScope.b = 0; try { $rootScope.$digest(); throw Error('Should have thrown exception'); } catch(e) { expect(e.message.match(/"fn: (watcherA|function)/g).length).toBe(10); } })); it('should prevent infinite loop when creating and resolving a promise in a watched expression', function() { module(function($rootScopeProvider) { $rootScopeProvider.digestTtl(10); }); inject(function($rootScope, $q) { var d = $q.defer(); d.resolve('Hello, world.'); $rootScope.$watch(function () { var $d2 = $q.defer(); $d2.resolve('Goodbye.'); $d2.promise.then(function () { }); return d.promise; }, function () { return 0; }); expect(function() { $rootScope.$digest(); }).toThrowMinErr('$rootScope', 'infdig', '10 $digest() iterations reached. Aborting!\n'+ 'Watchers fired in the last 5 iterations: []'); expect($rootScope.$$phase).toBeNull(); }); }); it('should not fire upon $watch registration on initial $digest', inject(function($rootScope) { var log = ''; $rootScope.a = 1; $rootScope.$watch('a', function() { log += 'a'; }); $rootScope.$watch('b', function() { log += 'b'; }); $rootScope.$digest(); log = ''; $rootScope.$digest(); expect(log).toEqual(''); })); it('should watch objects', inject(function($rootScope) { var log = ''; $rootScope.a = []; $rootScope.b = {}; $rootScope.$watch('a', function(value) { log +='.'; expect(value).toBe($rootScope.a); }, true); $rootScope.$watch('b', function(value) { log +='!'; expect(value).toBe($rootScope.b); }, true); $rootScope.$digest(); log = ''; $rootScope.a.push({}); $rootScope.b.name = ''; $rootScope.$digest(); expect(log).toEqual('.!'); })); it('should watch functions', function() { module(provideLog); inject(function($rootScope, log) { $rootScope.fn = function() {return 'a';}; $rootScope.$watch('fn', function(fn) { log(fn()); }); $rootScope.$digest(); expect(log).toEqual('a'); $rootScope.fn = function() {return 'b';}; $rootScope.$digest(); expect(log).toEqual('a; b'); }); }); it('should prevent $digest recursion', inject(function($rootScope) { var callCount = 0; $rootScope.$watch('name', function() { expect(function() { $rootScope.$digest(); }).toThrowMinErr('$rootScope', 'inprog', '$digest already in progress'); callCount++; }); $rootScope.name = 'a'; $rootScope.$digest(); expect(callCount).toEqual(1); })); it('should allow a watch to be added while in a digest', inject(function($rootScope) { var watch1 = jasmine.createSpy('watch1'), watch2 = jasmine.createSpy('watch2'); $rootScope.$watch('foo', function() { $rootScope.$watch('foo', watch1); $rootScope.$watch('foo', watch2); }); $rootScope.$apply('foo = true'); expect(watch1).toHaveBeenCalled(); expect(watch2).toHaveBeenCalled(); })); it('should not infinitely digest when current value is NaN', inject(function($rootScope) { $rootScope.$watch(function() { return NaN;}); expect(function() { $rootScope.$digest(); }).not.toThrow(); })); it('should always call the watcher with newVal and oldVal equal on the first run', inject(function($rootScope) { var log = []; function logger(scope, newVal, oldVal) { var val = (newVal === oldVal || (newVal !== oldVal && oldVal !== newVal)) ? newVal : 'xxx'; log.push(val); } $rootScope.$watch(function() { return NaN;}, logger); $rootScope.$watch(function() { return undefined;}, logger); $rootScope.$watch(function() { return '';}, logger); $rootScope.$watch(function() { return false;}, logger); $rootScope.$watch(function() { return {};}, logger, true); $rootScope.$watch(function() { return 23;}, logger); $rootScope.$digest(); expect(isNaN(log.shift())).toBe(true); //jasmine's toBe and toEqual don't work well with NaNs expect(log).toEqual([undefined, '', false, {}, 23]); log = []; $rootScope.$digest(); expect(log).toEqual([]); })); describe('$watch deregistration', function() { it('should return a function that allows listeners to be deregistered', inject( function($rootScope) { var listener = jasmine.createSpy('watch listener'), listenerRemove; listenerRemove = $rootScope.$watch('foo', listener); $rootScope.$digest(); //init expect(listener).toHaveBeenCalled(); expect(listenerRemove).toBeDefined(); listener.reset(); $rootScope.foo = 'bar'; $rootScope.$digest(); //triger expect(listener).toHaveBeenCalledOnce(); listener.reset(); $rootScope.foo = 'baz'; listenerRemove(); $rootScope.$digest(); //trigger expect(listener).not.toHaveBeenCalled(); })); it('should allow a watch to be deregistered while in a digest', inject(function($rootScope) { var remove1, remove2; $rootScope.$watch('remove', function() { remove1(); remove2(); }); remove1 = $rootScope.$watch('thing', function() {}); remove2 = $rootScope.$watch('thing', function() {}); expect(function() { $rootScope.$apply('remove = true'); }).not.toThrow(); })); it('should not mess up the digest loop if deregistration happens during digest', inject( function($rootScope, log) { // we are testing this due to regression #5525 which is related to how the digest loops lastDirtyWatch // short-circuiting optimization works // scenario: watch1 deregistering watch1 var scope = $rootScope.$new(); var deregWatch1 = scope.$watch(log.fn('watch1'), function() { deregWatch1(); log('watchAction1'); }); scope.$watch(log.fn('watch2'), log.fn('watchAction2')); scope.$watch(log.fn('watch3'), log.fn('watchAction3')); $rootScope.$digest(); expect(log).toEqual(['watch1', 'watchAction1', 'watch2', 'watchAction2', 'watch3', 'watchAction3', 'watch2', 'watch3']); scope.$destroy(); log.reset(); // scenario: watch1 deregistering watch2 scope = $rootScope.$new(); scope.$watch(log.fn('watch1'), function() { deregWatch2(); log('watchAction1'); }); var deregWatch2 = scope.$watch(log.fn('watch2'), log.fn('watchAction2')); scope.$watch(log.fn('watch3'), log.fn('watchAction3')); $rootScope.$digest(); expect(log).toEqual(['watch1', 'watchAction1', 'watch1', 'watch3', 'watchAction3', 'watch1', 'watch3']); scope.$destroy(); log.reset(); // scenario: watch2 deregistering watch1 scope = $rootScope.$new(); deregWatch1 = scope.$watch(log.fn('watch1'), log.fn('watchAction1')); scope.$watch(log.fn('watch2'), function() { deregWatch1(); log('watchAction2'); }); scope.$watch(log.fn('watch3'), log.fn('watchAction3')); $rootScope.$digest(); expect(log).toEqual(['watch1', 'watchAction1', 'watch2', 'watchAction2', 'watch3', 'watchAction3', 'watch2', 'watch3']); })); }); describe('$watchCollection', function() { var log, $rootScope, deregister; beforeEach(inject(function(_$rootScope_) { log = []; $rootScope = _$rootScope_; deregister = $rootScope.$watchCollection('obj', function logger(obj) { log.push(toJson(obj)); }); })); it('should not trigger if nothing change', inject(function($rootScope) { $rootScope.$digest(); expect(log).toEqual([undefined]); $rootScope.$digest(); expect(log).toEqual([undefined]); })); it('should allow deregistration', inject(function($rootScope) { $rootScope.obj = []; $rootScope.$digest(); expect(log).toEqual(['[]']); $rootScope.obj.push('a'); deregister(); $rootScope.$digest(); expect(log).toEqual(['[]']); })); describe('array', function() { it('should trigger when property changes into array', function() { $rootScope.obj = 'test'; $rootScope.$digest(); expect(log).toEqual(['"test"']); $rootScope.obj = []; $rootScope.$digest(); expect(log).toEqual(['"test"', '[]']); $rootScope.obj = {}; $rootScope.$digest(); expect(log).toEqual(['"test"', '[]', '{}']); $rootScope.obj = []; $rootScope.$digest(); expect(log).toEqual(['"test"', '[]', '{}', '[]']); $rootScope.obj = undefined; $rootScope.$digest(); expect(log).toEqual(['"test"', '[]', '{}', '[]', undefined]); }); it('should not trigger change when object in collection changes', function() { $rootScope.obj = [{}]; $rootScope.$digest(); expect(log).toEqual(['[{}]']); $rootScope.obj[0].name = 'foo'; $rootScope.$digest(); expect(log).toEqual(['[{}]']); }); it('should watch array properties', function() { $rootScope.obj = []; $rootScope.$digest(); expect(log).toEqual(['[]']); $rootScope.obj.push('a'); $rootScope.$digest(); expect(log).toEqual(['[]', '["a"]']); $rootScope.obj[0] = 'b'; $rootScope.$digest(); expect(log).toEqual(['[]', '["a"]', '["b"]']); $rootScope.obj.push([]); $rootScope.obj.push({}); log = []; $rootScope.$digest(); expect(log).toEqual(['["b",[],{}]']); var temp = $rootScope.obj[1]; $rootScope.obj[1] = $rootScope.obj[2]; $rootScope.obj[2] = temp; $rootScope.$digest(); expect(log).toEqual([ '["b",[],{}]', '["b",{},[]]' ]); $rootScope.obj.shift(); log = []; $rootScope.$digest(); expect(log).toEqual([ '[{},[]]' ]); }); it('should watch array-like objects like arrays', function () { var arrayLikelog = []; $rootScope.$watchCollection('arrayLikeObject', function logger(obj) { forEach(obj, function (element){ arrayLikelog.push(element.name); }); }); document.body.innerHTML = "

                      " + "a" + "b" + "

                      "; $rootScope.arrayLikeObject = document.getElementsByTagName('a'); $rootScope.$digest(); expect(arrayLikelog).toEqual(['x', 'y']); }); }); describe('object', function() { it('should trigger when property changes into object', function() { $rootScope.obj = 'test'; $rootScope.$digest(); expect(log).toEqual(['"test"']); $rootScope.obj = {}; $rootScope.$digest(); expect(log).toEqual(['"test"', '{}']); }); it('should not trigger change when object in collection changes', function() { $rootScope.obj = {name: {}}; $rootScope.$digest(); expect(log).toEqual(['{"name":{}}']); $rootScope.obj.name.bar = 'foo'; $rootScope.$digest(); expect(log).toEqual(['{"name":{}}']); }); it('should watch object properties', function() { $rootScope.obj = {}; $rootScope.$digest(); expect(log).toEqual(['{}']); $rootScope.obj.a= 'A'; $rootScope.$digest(); expect(log).toEqual(['{}', '{"a":"A"}']); $rootScope.obj.a = 'B'; $rootScope.$digest(); expect(log).toEqual(['{}', '{"a":"A"}', '{"a":"B"}']); $rootScope.obj.b = []; $rootScope.obj.c = {}; log = []; $rootScope.$digest(); expect(log).toEqual(['{"a":"B","b":[],"c":{}}']); var temp = $rootScope.obj.a; $rootScope.obj.a = $rootScope.obj.b; $rootScope.obj.c = temp; $rootScope.$digest(); expect(log).toEqual([ '{"a":"B","b":[],"c":{}}', '{"a":[],"b":[],"c":"B"}' ]); delete $rootScope.obj.a; log = []; $rootScope.$digest(); expect(log).toEqual([ '{"b":[],"c":"B"}' ]); }); }); }); describe('optimizations', function() { function setupWatches(scope, log) { scope.$watch(function() { log('w1'); return scope.w1; }, log.fn('w1action')); scope.$watch(function() { log('w2'); return scope.w2; }, log.fn('w2action')); scope.$watch(function() { log('w3'); return scope.w3; }, log.fn('w3action')); scope.$digest(); log.reset(); } it('should check watches only once during an empty digest', inject(function(log, $rootScope) { setupWatches($rootScope, log); $rootScope.$digest(); expect(log).toEqual(['w1', 'w2', 'w3']); })); it('should quit digest early after we check the last watch that was previously dirty', inject(function(log, $rootScope) { setupWatches($rootScope, log); $rootScope.w1 = 'x'; $rootScope.$digest(); expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w1']); })); it('should not quit digest early if a new watch was added from an existing watch action', inject(function(log, $rootScope) { setupWatches($rootScope, log); $rootScope.$watch(log.fn('w4'), function() { log('w4action'); $rootScope.$watch(log.fn('w5'), log.fn('w5action')); }); $rootScope.$digest(); expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'w1', 'w2', 'w3', 'w4', 'w5', 'w5action', 'w1', 'w2', 'w3', 'w4', 'w5']); })); it('should not quit digest early if an evalAsync task was scheduled from a watch action', inject(function(log, $rootScope) { setupWatches($rootScope, log); $rootScope.$watch(log.fn('w4'), function() { log('w4action'); $rootScope.$evalAsync(function() { log('evalAsync') }); }); $rootScope.$digest(); expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'evalAsync', 'w1', 'w2', 'w3', 'w4']); })); it('should quit digest early but not too early when various watches fire', inject(function(log, $rootScope) { setupWatches($rootScope, log); $rootScope.$watch(function() { log('w4'); return $rootScope.w4; }, function(newVal) { log('w4action'); $rootScope.w2 = newVal; }); $rootScope.$digest(); log.reset(); $rootScope.w1 = 'x'; $rootScope.w4 = 'x'; $rootScope.$digest(); expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w4', 'w4action', 'w1', 'w2', 'w2action', 'w3', 'w4', 'w1', 'w2']); })); }); }); describe('$destroy', function() { var first = null, middle = null, last = null, log = null; beforeEach(inject(function($rootScope) { log = ''; first = $rootScope.$new(); middle = $rootScope.$new(); last = $rootScope.$new(); first.$watch(function() { log += '1';}); middle.$watch(function() { log += '2';}); last.$watch(function() { log += '3';}); $rootScope.$digest(); log = ''; })); it('should broadcast $destroy on rootScope', inject(function($rootScope) { var spy = spyOn(angular, 'noop'); $rootScope.$on('$destroy', angular.noop); $rootScope.$destroy(); $rootScope.$digest(); expect(log).toEqual('123'); expect(spy).toHaveBeenCalled(); expect($rootScope.$$destroyed).toBe(true); })); it('should remove first', inject(function($rootScope) { first.$destroy(); $rootScope.$digest(); expect(log).toEqual('23'); })); it('should remove middle', inject(function($rootScope) { middle.$destroy(); $rootScope.$digest(); expect(log).toEqual('13'); })); it('should remove last', inject(function($rootScope) { last.$destroy(); $rootScope.$digest(); expect(log).toEqual('12'); })); it('should broadcast the $destroy event', inject(function($rootScope, log) { first.$on('$destroy', log.fn('first')); first.$new().$on('$destroy', log.fn('first-child')); first.$destroy(); expect(log).toEqual('first; first-child'); })); it('should $destroy a scope only once and ignore any further destroy calls', inject(function($rootScope) { $rootScope.$digest(); expect(log).toBe('123'); first.$destroy(); first.$apply(); expect(log).toBe('12323'); first.$destroy(); first.$destroy(); first.$apply(); expect(log).toBe('1232323'); })); it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) { var EVENT = 'fooEvent', spy = jasmine.createSpy('listener'), firstSecond = first.$new(); firstSecond.$on(EVENT, spy); firstSecond.$on(EVENT, spy); middle.$on(EVENT, spy); expect($rootScope.$$listenerCount[EVENT]).toBe(3); expect(first.$$listenerCount[EVENT]).toBe(2); firstSecond.$destroy(); expect($rootScope.$$listenerCount[EVENT]).toBe(1); expect(first.$$listenerCount[EVENT]).toBeUndefined(); $rootScope.$broadcast(EVENT); expect(spy.callCount).toBe(1); })); }); describe('$eval', function() { it('should eval an expression', inject(function($rootScope) { expect($rootScope.$eval('a=1')).toEqual(1); expect($rootScope.a).toEqual(1); $rootScope.$eval(function(self) {self.b=2;}); expect($rootScope.b).toEqual(2); })); it('should allow passing locals to the expression', inject(function($rootScope) { expect($rootScope.$eval('a+1', {a: 2})).toBe(3); $rootScope.$eval(function(scope, locals) { scope.c = locals.b + 4; }, {b: 3}); expect($rootScope.c).toBe(7); })); }); describe('$evalAsync', function() { it('should run callback before $watch', inject(function($rootScope) { var log = ''; var child = $rootScope.$new(); $rootScope.$evalAsync(function(scope) { log += 'parent.async;'; }); $rootScope.$watch('value', function() { log += 'parent.$digest;'; }); child.$evalAsync(function(scope) { log += 'child.async;'; }); child.$watch('value', function() { log += 'child.$digest;'; }); $rootScope.$digest(); expect(log).toEqual('parent.async;child.async;parent.$digest;child.$digest;'); })); it('should not run another digest for an $$postDigest call', inject(function($rootScope) { var internalWatchCount = 0; var externalWatchCount = 0; $rootScope.internalCount = 0; $rootScope.externalCount = 0; $rootScope.$evalAsync(function(scope) { $rootScope.internalCount++; }); $rootScope.$$postDigest(function(scope) { $rootScope.externalCount++; }); $rootScope.$watch('internalCount', function(value) { internalWatchCount = value; }); $rootScope.$watch('externalCount', function(value) { externalWatchCount = value; }); $rootScope.$digest(); expect(internalWatchCount).toEqual(1); expect(externalWatchCount).toEqual(0); })); it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) { var parent = $rootScope.$new(), child = parent.$new(), count = 0; $rootScope.$$postDigest(function() { count++; }); parent.$$postDigest(function() { count++; }); child.$$postDigest(function() { count++; }); expect(count).toBe(0); $rootScope.$digest(); expect(count).toBe(3); })); it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) { var parent = $rootScope.$new(), child = parent.$new(true), signature = ''; parent.$$postDigest(function() { signature += 'A'; }); child.$$postDigest(function() { signature += 'B'; }); expect(signature).toBe(''); $rootScope.$digest(); expect(signature).toBe('AB'); })); it('should cause a $digest rerun', inject(function($rootScope) { $rootScope.log = ''; $rootScope.value = 0; $rootScope.$watch('value', 'log = log + ".";'); $rootScope.$watch('init', function() { $rootScope.$evalAsync('value = 123; log = log + "=" '); expect($rootScope.value).toEqual(0); }); $rootScope.$digest(); expect($rootScope.log).toEqual('.=.'); })); it('should run async in the same order as added', inject(function($rootScope) { $rootScope.log = ''; $rootScope.$evalAsync("log = log + 1"); $rootScope.$evalAsync("log = log + 2"); $rootScope.$digest(); expect($rootScope.log).toBe('12'); })); it('should run async expressions in their proper context', inject(function ($rootScope) { var child = $rootScope.$new(); $rootScope.ctx = 'root context'; $rootScope.log = ''; child.ctx = 'child context'; child.log = ''; child.$evalAsync('log=ctx'); $rootScope.$digest(); expect($rootScope.log).toBe(''); expect(child.log).toBe('child context'); })); it('should operate only with a single queue across all child and isolate scopes', inject(function($rootScope) { var childScope = $rootScope.$new(); var isolateScope = $rootScope.$new(true); $rootScope.$evalAsync('rootExpression'); childScope.$evalAsync('childExpression'); isolateScope.$evalAsync('isolateExpression'); expect(childScope.$$asyncQueue).toBe($rootScope.$$asyncQueue); expect(isolateScope.$$asyncQueue).toBe($rootScope.$$asyncQueue); expect($rootScope.$$asyncQueue).toEqual([ {scope: $rootScope, expression: 'rootExpression'}, {scope: childScope, expression: 'childExpression'}, {scope: isolateScope, expression: 'isolateExpression'}]); })); describe('auto-flushing when queueing outside of an $apply', function() { var log, $rootScope, $browser; beforeEach(inject(function(_log_, _$rootScope_, _$browser_) { log = _log_; $rootScope = _$rootScope_; $browser = _$browser_; })); it('should auto-flush the queue asynchronously and trigger digest', function() { $rootScope.$evalAsync(log.fn('eval-ed!')); $rootScope.$watch(log.fn('digesting')); expect(log).toEqual([]); $browser.defer.flush(0); expect(log).toEqual(['eval-ed!', 'digesting', 'digesting']); }); it('should not trigger digest asynchronously if the queue is empty in the next tick', function() { $rootScope.$evalAsync(log.fn('eval-ed!')); $rootScope.$watch(log.fn('digesting')); expect(log).toEqual([]); $rootScope.$digest(); expect(log).toEqual(['eval-ed!', 'digesting', 'digesting']); log.reset(); $browser.defer.flush(0); expect(log).toEqual([]); }); it('should not schedule more than one auto-flush task', function() { $rootScope.$evalAsync(log.fn('eval-ed 1!')); $rootScope.$evalAsync(log.fn('eval-ed 2!')); $browser.defer.flush(0); expect(log).toEqual(['eval-ed 1!', 'eval-ed 2!']); $browser.defer.flush(100000); expect(log).toEqual(['eval-ed 1!', 'eval-ed 2!']); }); }); }); describe('$apply', function() { it('should apply expression with full lifecycle', inject(function($rootScope) { var log = ''; var child = $rootScope.$new(); $rootScope.$watch('a', function(a) { log += '1'; }); child.$apply('$parent.a=0'); expect(log).toEqual('1'); })); it('should catch exceptions', function() { module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); }); inject(function($rootScope, $exceptionHandler, $log) { var log = ''; var child = $rootScope.$new(); $rootScope.$watch('a', function(a) { log += '1'; }); $rootScope.a = 0; child.$apply(function() { throw new Error('MyError'); }); expect(log).toEqual('1'); expect($exceptionHandler.errors[0].message).toEqual('MyError'); $log.error.logs.shift(); }); }); it('should log exceptions from $digest', function() { module(function($rootScopeProvider, $exceptionHandlerProvider) { $rootScopeProvider.digestTtl(2); $exceptionHandlerProvider.mode('log'); }); inject(function($rootScope, $exceptionHandler) { $rootScope.$watch('a', function() {$rootScope.b++;}); $rootScope.$watch('b', function() {$rootScope.a++;}); $rootScope.a = $rootScope.b = 0; expect(function() { $rootScope.$apply(); }).toThrow(); expect($exceptionHandler.errors[0]).toBeDefined(); expect($rootScope.$$phase).toBeNull(); }); }); describe('exceptions', function() { var log; beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); beforeEach(inject(function($rootScope) { log = ''; $rootScope.$watch(function() { log += '$digest;'; }); $rootScope.$digest(); log = ''; })); it('should execute and return value and update', inject( function($rootScope, $exceptionHandler) { $rootScope.name = 'abc'; expect($rootScope.$apply(function(scope) { return scope.name; })).toEqual('abc'); expect(log).toEqual('$digest;'); expect($exceptionHandler.errors).toEqual([]); })); it('should catch exception and update', inject(function($rootScope, $exceptionHandler) { var error = new Error('MyError'); $rootScope.$apply(function() { throw error; }); expect(log).toEqual('$digest;'); expect($exceptionHandler.errors).toEqual([error]); })); }); describe('recursive $apply protection', function() { it('should throw an exception if $apply is called while an $apply is in progress', inject( function($rootScope) { expect(function() { $rootScope.$apply(function() { $rootScope.$apply(); }); }).toThrowMinErr('$rootScope', 'inprog', '$apply already in progress'); })); it('should throw an exception if $apply is called while flushing evalAsync queue', inject( function($rootScope) { expect(function() { $rootScope.$apply(function() { $rootScope.$evalAsync(function() { $rootScope.$apply(); }); }); }).toThrowMinErr('$rootScope', 'inprog', '$digest already in progress'); })); it('should throw an exception if $apply is called while a watch is being initialized', inject( function($rootScope) { var childScope1 = $rootScope.$new(); childScope1.$watch('x', function() { childScope1.$apply(); }); expect(function() { childScope1.$apply(); }).toThrowMinErr('$rootScope', 'inprog', '$digest already in progress'); })); it('should thrown an exception if $apply in called from a watch fn (after init)', inject( function($rootScope) { var childScope2 = $rootScope.$new(); childScope2.$apply(function() { childScope2.$watch('x', function(newVal, oldVal) { if (newVal !== oldVal) { childScope2.$apply(); } }); }); expect(function() { childScope2.$apply(function() { childScope2.x = 'something'; }); }).toThrowMinErr('$rootScope', 'inprog', '$digest already in progress'); })); }); }); describe('events', function() { describe('$on', function() { it('should add listener for both $emit and $broadcast events', inject(function($rootScope) { var log = '', child = $rootScope.$new(); function eventFn() { log += 'X'; } child.$on('abc', eventFn); expect(log).toEqual(''); child.$emit('abc'); expect(log).toEqual('X'); child.$broadcast('abc'); expect(log).toEqual('XX'); })); it('should increment ancestor $$listenerCount entries', inject(function($rootScope) { var child1 = $rootScope.$new(), child2 = child1.$new(), spy = jasmine.createSpy(); $rootScope.$on('event1', spy); expect($rootScope.$$listenerCount).toEqual({event1: 1}); child1.$on('event1', spy); expect($rootScope.$$listenerCount).toEqual({event1: 2}); expect(child1.$$listenerCount).toEqual({event1: 1}); child2.$on('event2', spy); expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1}); expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1}); expect(child2.$$listenerCount).toEqual({event2: 1}); })); describe('deregistration', function() { it('should return a function that deregisters the listener', inject(function($rootScope) { var log = '', child = $rootScope.$new(), listenerRemove; function eventFn() { log += 'X'; } listenerRemove = child.$on('abc', eventFn); expect(log).toEqual(''); expect(listenerRemove).toBeDefined(); child.$emit('abc'); child.$broadcast('abc'); expect(log).toEqual('XX'); expect($rootScope.$$listenerCount['abc']).toBe(1); log = ''; listenerRemove(); child.$emit('abc'); child.$broadcast('abc'); expect(log).toEqual(''); expect($rootScope.$$listenerCount['abc']).toBeUndefined(); })); it('should decrement ancestor $$listenerCount entries', inject(function($rootScope) { var child1 = $rootScope.$new(), child2 = child1.$new(), spy = jasmine.createSpy(); $rootScope.$on('event1', spy); expect($rootScope.$$listenerCount).toEqual({event1: 1}); child1.$on('event1', spy); expect($rootScope.$$listenerCount).toEqual({event1: 2}); expect(child1.$$listenerCount).toEqual({event1: 1}); var deregisterEvent2Listener = child2.$on('event2', spy); expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1}); expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1}); expect(child2.$$listenerCount).toEqual({event2: 1}); deregisterEvent2Listener(); expect($rootScope.$$listenerCount).toEqual({event1: 2}); expect(child1.$$listenerCount).toEqual({event1: 1}); expect(child2.$$listenerCount).toEqual({}); })); }); }); describe('$emit', function() { var log, child, grandChild, greatGrandChild; function logger(event) { log += event.currentScope.id + '>'; } beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); beforeEach(inject(function($rootScope) { log = ''; child = $rootScope.$new(); grandChild = child.$new(); greatGrandChild = grandChild.$new(); $rootScope.id = 0; child.id = 1; grandChild.id = 2; greatGrandChild.id = 3; $rootScope.$on('myEvent', logger); child.$on('myEvent', logger); grandChild.$on('myEvent', logger); greatGrandChild.$on('myEvent', logger); })); it('should bubble event up to the root scope', function() { grandChild.$emit('myEvent'); expect(log).toEqual('2>1>0>'); }); it('should allow all events on the same scope to run even if stopPropagation is called', function(){ child.$on('myEvent', logger); grandChild.$on('myEvent', function(e) { e.stopPropagation(); }); grandChild.$on('myEvent', logger); grandChild.$on('myEvent', logger); grandChild.$emit('myEvent'); expect(log).toEqual('2>2>2>'); }); it('should dispatch exceptions to the $exceptionHandler', inject(function($exceptionHandler) { child.$on('myEvent', function() { throw 'bubbleException'; }); grandChild.$emit('myEvent'); expect(log).toEqual('2>1>0>'); expect($exceptionHandler.errors).toEqual(['bubbleException']); })); it('should allow stopping event propagation', function() { child.$on('myEvent', function(event) { event.stopPropagation(); }); grandChild.$emit('myEvent'); expect(log).toEqual('2>1>'); }); it('should forward method arguments', function() { child.$on('abc', function(event, arg1, arg2) { expect(event.name).toBe('abc'); expect(arg1).toBe('arg1'); expect(arg2).toBe('arg2'); }); child.$emit('abc', 'arg1', 'arg2'); }); it('should allow removing event listener inside a listener on $emit', function() { var spy1 = jasmine.createSpy('1st listener'); var spy2 = jasmine.createSpy('2nd listener'); var spy3 = jasmine.createSpy('3rd listener'); var remove1 = child.$on('evt', spy1); var remove2 = child.$on('evt', spy2); var remove3 = child.$on('evt', spy3); spy1.andCallFake(remove1); expect(child.$$listeners['evt'].length).toBe(3); // should call all listeners and remove 1st child.$emit('evt'); expect(spy1).toHaveBeenCalledOnce(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).toHaveBeenCalledOnce(); expect(child.$$listeners['evt'].length).toBe(3); // cleanup will happen on next $emit spy1.reset(); spy2.reset(); spy3.reset(); // should call only 2nd because 1st was already removed and 2nd removes 3rd spy2.andCallFake(remove3); child.$emit('evt'); expect(spy1).not.toHaveBeenCalled(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).not.toHaveBeenCalled(); expect(child.$$listeners['evt'].length).toBe(1); }); it('should allow removing event listener inside a listener on $broadcast', function() { var spy1 = jasmine.createSpy('1st listener'); var spy2 = jasmine.createSpy('2nd listener'); var spy3 = jasmine.createSpy('3rd listener'); var remove1 = child.$on('evt', spy1); var remove2 = child.$on('evt', spy2); var remove3 = child.$on('evt', spy3); spy1.andCallFake(remove1); expect(child.$$listeners['evt'].length).toBe(3); // should call all listeners and remove 1st child.$broadcast('evt'); expect(spy1).toHaveBeenCalledOnce(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).toHaveBeenCalledOnce(); expect(child.$$listeners['evt'].length).toBe(3); //cleanup will happen on next $broadcast spy1.reset(); spy2.reset(); spy3.reset(); // should call only 2nd because 1st was already removed and 2nd removes 3rd spy2.andCallFake(remove3); child.$broadcast('evt'); expect(spy1).not.toHaveBeenCalled(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).not.toHaveBeenCalled(); expect(child.$$listeners['evt'].length).toBe(1); }); describe('event object', function() { it('should have methods/properties', function() { var event; child.$on('myEvent', function(e) { expect(e.targetScope).toBe(grandChild); expect(e.currentScope).toBe(child); expect(e.name).toBe('myEvent'); event = e; }); grandChild.$emit('myEvent'); expect(event).toBeDefined(); }); it('should have preventDefault method and defaultPrevented property', function() { var event = grandChild.$emit('myEvent'); expect(event.defaultPrevented).toBe(false); child.$on('myEvent', function(event) { event.preventDefault(); }); event = grandChild.$emit('myEvent'); expect(event.defaultPrevented).toBe(true); }); }); }); describe('$broadcast', function() { describe('event propagation', function() { var log, child1, child2, child3, grandChild11, grandChild21, grandChild22, grandChild23, greatGrandChild211; function logger(event) { log += event.currentScope.id + '>'; } beforeEach(inject(function($rootScope) { log = ''; child1 = $rootScope.$new(); child2 = $rootScope.$new(); child3 = $rootScope.$new(); grandChild11 = child1.$new(); grandChild21 = child2.$new(); grandChild22 = child2.$new(); grandChild23 = child2.$new(); greatGrandChild211 = grandChild21.$new(); $rootScope.id = 0; child1.id = 1; child2.id = 2; child3.id = 3; grandChild11.id = 11; grandChild21.id = 21; grandChild22.id = 22; grandChild23.id = 23; greatGrandChild211.id = 211; $rootScope.$on('myEvent', logger); child1.$on('myEvent', logger); child2.$on('myEvent', logger); child3.$on('myEvent', logger); grandChild11.$on('myEvent', logger); grandChild21.$on('myEvent', logger); grandChild22.$on('myEvent', logger); grandChild23.$on('myEvent', logger); greatGrandChild211.$on('myEvent', logger); // R // / | \ // 1 2 3 // / / | \ // 11 21 22 23 // | // 211 })); it('should broadcast an event from the root scope', inject(function($rootScope) { $rootScope.$broadcast('myEvent'); expect(log).toBe('0>1>11>2>21>211>22>23>3>'); })); it('should broadcast an event from a child scope', function() { child2.$broadcast('myEvent'); expect(log).toBe('2>21>211>22>23>'); }); it('should broadcast an event from a leaf scope with a sibling', function() { grandChild22.$broadcast('myEvent'); expect(log).toBe('22>'); }); it('should broadcast an event from a leaf scope without a sibling', function() { grandChild23.$broadcast('myEvent'); expect(log).toBe('23>'); }); it('should not not fire any listeners for other events', inject(function($rootScope) { $rootScope.$broadcast('fooEvent'); expect(log).toBe(''); })); it('should not descend past scopes with a $$listerCount of 0 or undefined', inject(function($rootScope) { var EVENT = 'fooEvent', spy = jasmine.createSpy('listener'); // Precondition: There should be no listeners for fooEvent. expect($rootScope.$$listenerCount[EVENT]).toBeUndefined(); // Add a spy listener to a child scope. $rootScope.$$childHead.$$listeners[EVENT] = [spy]; // $rootScope's count for 'fooEvent' is undefined, so spy should not be called. $rootScope.$broadcast(EVENT); expect(spy).not.toHaveBeenCalled(); })); it('should return event object', function() { var result = child1.$broadcast('some'); expect(result).toBeDefined(); expect(result.name).toBe('some'); expect(result.targetScope).toBe(child1); }); }); describe('listener', function() { it('should receive event object', inject(function($rootScope) { var scope = $rootScope, child = scope.$new(), event; child.$on('fooEvent', function(e) { event = e; }); scope.$broadcast('fooEvent'); expect(event.name).toBe('fooEvent'); expect(event.targetScope).toBe(scope); expect(event.currentScope).toBe(child); })); it('should support passing messages as varargs', inject(function($rootScope) { var scope = $rootScope, child = scope.$new(), args; child.$on('fooEvent', function() { args = arguments; }); scope.$broadcast('fooEvent', 'do', 're', 'me', 'fa'); expect(args.length).toBe(5); expect(sliceArgs(args, 1)).toEqual(['do', 're', 'me', 'fa']); })); }); }); }); describe("doc examples", function() { it("should properly fire off watch listeners upon scope changes", inject(function($rootScope) { // var scope = $rootScope.$new(); scope.salutation = 'Hello'; scope.name = 'World'; expect(scope.greeting).toEqual(undefined); scope.$watch('name', function() { scope.greeting = scope.salutation + ' ' + scope.name + '!'; }); // initialize the watch expect(scope.greeting).toEqual(undefined); scope.name = 'Misko'; // still old value, since watches have not been called yet expect(scope.greeting).toEqual(undefined); scope.$digest(); // fire all the watches expect(scope.greeting).toEqual('Hello Misko!'); // })); }); }); angular.js-1.2.11/test/ng/sanitizeUriSpec.js000066400000000000000000000172021227375216300207140ustar00rootroot00000000000000'use strict'; describe('sanitizeUri', function() { var sanitizeHref, sanitizeImg, sanitizeUriProvider, testUrl; beforeEach(function() { module(function(_$$sanitizeUriProvider_) { sanitizeUriProvider = _$$sanitizeUriProvider_; }); inject(function($$sanitizeUri) { sanitizeHref = function(uri) { return $$sanitizeUri(uri, false); }; sanitizeImg = function(uri) { return $$sanitizeUri(uri, true); } }); }); function isEvilInCurrentBrowser(uri) { var a = document.createElement('a'); a.setAttribute('href', uri); return a.href.substring(0, 4) !== 'http'; } describe('img[src] sanitization', function() { it('should sanitize javascript: urls', function() { testUrl = "javascript:doEvilStuff()"; expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); }); it('should sanitize non-image data: urls', function() { testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');"; expect(sanitizeImg(testUrl)).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');"); testUrl = "data:,foo"; expect(sanitizeImg(testUrl)).toBe("unsafe:data:,foo"); }); it('should not sanitize data: URIs for images', function() { // image data uri // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever testUrl = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; expect(sanitizeImg(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); }); it('should sanitize mailto: urls', function() { testUrl = "mailto:foo@bar.com"; expect(sanitizeImg(testUrl)).toBe('unsafe:mailto:foo@bar.com'); }); it('should sanitize obfuscated javascript: urls', function() { // case-sensitive testUrl = "JaVaScRiPt:doEvilStuff()"; expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // tab in protocol testUrl = "java\u0009script:doEvilStuff()"; if (isEvilInCurrentBrowser(testUrl)) { expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // space before testUrl = " javascript:doEvilStuff()"; expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // ws chars before testUrl = " \u000e javascript:doEvilStuff()"; if (isEvilInCurrentBrowser(testUrl)) { expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // post-fixed with proper url testUrl = "javascript:doEvilStuff(); http://make.me/look/good"; expect(sanitizeImg(testUrl)).toBeOneOf( 'unsafe:javascript:doEvilStuff(); http://make.me/look/good', 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good' ); }); it('should sanitize ng-src bindings as well', function() { testUrl = "javascript:doEvilStuff()"; expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); }); it('should not sanitize valid urls', function() { testUrl = "foo/bar"; expect(sanitizeImg(testUrl)).toBe('foo/bar'); testUrl = "/foo/bar"; expect(sanitizeImg(testUrl)).toBe('/foo/bar'); testUrl = "../foo/bar"; expect(sanitizeImg(testUrl)).toBe('../foo/bar'); testUrl = "#foo"; expect(sanitizeImg(testUrl)).toBe('#foo'); testUrl = "http://foo.com/bar"; expect(sanitizeImg(testUrl)).toBe('http://foo.com/bar'); testUrl = " http://foo.com/bar"; expect(sanitizeImg(testUrl)).toBe(' http://foo.com/bar'); testUrl = "https://foo.com/bar"; expect(sanitizeImg(testUrl)).toBe('https://foo.com/bar'); testUrl = "ftp://foo.com/bar"; expect(sanitizeImg(testUrl)).toBe('ftp://foo.com/bar'); testUrl = "file:///foo/bar.html"; expect(sanitizeImg(testUrl)).toBe('file:///foo/bar.html'); }); it('should allow reconfiguration of the src whitelist', function() { var returnVal; expect(sanitizeUriProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true); returnVal = sanitizeUriProvider.imgSrcSanitizationWhitelist(/javascript:/); expect(returnVal).toBe(sanitizeUriProvider); testUrl = "javascript:doEvilStuff()"; expect(sanitizeImg(testUrl)).toBe('javascript:doEvilStuff()'); testUrl = "http://recon/figured"; expect(sanitizeImg(testUrl)).toBe('unsafe:http://recon/figured'); }); }); describe('a[href] sanitization', function() { it('should sanitize javascript: urls', inject(function() { testUrl = "javascript:doEvilStuff()"; expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); })); it('should sanitize data: urls', inject(function() { testUrl = "data:evilPayload"; expect(sanitizeHref(testUrl)).toBe('unsafe:data:evilPayload'); })); it('should sanitize obfuscated javascript: urls', inject(function() { // case-sensitive testUrl = "JaVaScRiPt:doEvilStuff()"; expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // tab in protocol testUrl = "java\u0009script:doEvilStuff()"; if (isEvilInCurrentBrowser(testUrl)) { expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // space before testUrl = " javascript:doEvilStuff()"; expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // ws chars before testUrl = " \u000e javascript:doEvilStuff()"; if (isEvilInCurrentBrowser(testUrl)) { expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // post-fixed with proper url testUrl = "javascript:doEvilStuff(); http://make.me/look/good"; expect(sanitizeHref(testUrl)).toBeOneOf( 'unsafe:javascript:doEvilStuff(); http://make.me/look/good', 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good' ); })); it('should sanitize ngHref bindings as well', inject(function() { testUrl = "javascript:doEvilStuff()"; expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); })); it('should not sanitize valid urls', inject(function() { testUrl = "foo/bar"; expect(sanitizeHref(testUrl)).toBe('foo/bar'); testUrl = "/foo/bar"; expect(sanitizeHref(testUrl)).toBe('/foo/bar'); testUrl = "../foo/bar"; expect(sanitizeHref(testUrl)).toBe('../foo/bar'); testUrl = "#foo"; expect(sanitizeHref(testUrl)).toBe('#foo'); testUrl = "http://foo/bar"; expect(sanitizeHref(testUrl)).toBe('http://foo/bar'); testUrl = " http://foo/bar"; expect(sanitizeHref(testUrl)).toBe(' http://foo/bar'); testUrl = "https://foo/bar"; expect(sanitizeHref(testUrl)).toBe('https://foo/bar'); testUrl = "ftp://foo/bar"; expect(sanitizeHref(testUrl)).toBe('ftp://foo/bar'); testUrl = "mailto:foo@bar.com"; expect(sanitizeHref(testUrl)).toBe('mailto:foo@bar.com'); testUrl = "file:///foo/bar.html"; expect(sanitizeHref(testUrl)).toBe('file:///foo/bar.html'); })); it('should allow reconfiguration of the href whitelist', function() { var returnVal; expect(sanitizeUriProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true); returnVal = sanitizeUriProvider.aHrefSanitizationWhitelist(/javascript:/); expect(returnVal).toBe(sanitizeUriProvider); testUrl = "javascript:doEvilStuff()"; expect(sanitizeHref(testUrl)).toBe('javascript:doEvilStuff()'); testUrl = "http://recon/figured"; expect(sanitizeHref(testUrl)).toBe('unsafe:http://recon/figured'); }); }); });angular.js-1.2.11/test/ng/sceSpecs.js000066400000000000000000000531241227375216300173460ustar00rootroot00000000000000'use strict'; describe('SCE', function() { // Work around an IE8 bug. Though window.inject === angular.mock.inject, if it's invoked the // window scope, IE8 loses the exception object that bubbles up and replaces it with a TypeError. // By using a local alias, it gets invoked on the global scope instead of window. // Ref: https://github.com/angular/angular.js/pull/4221#/issuecomment-25515813 var inject = angular.mock.inject; describe('when disabled', function() { beforeEach(function() { module(function($sceProvider) { $sceProvider.enabled(false); }); }); it('should provide the getter for enabled', inject(function($sce) { expect($sce.isEnabled()).toBe(false); })); it('should not wrap/unwrap any value or throw exception on non-string values', inject(function($sce) { var originalValue = { foo: "bar" }; expect($sce.trustAs($sce.JS, originalValue)).toBe(originalValue); expect($sce.getTrusted($sce.JS, originalValue)).toBe(originalValue); })); }); describe('IE8 quirks mode', function() { function runTest(enabled, documentMode, expectException) { module(function($provide) { $provide.value('$sniffer', { msie: documentMode, msieDocumentMode: documentMode }); $provide.value('$sceDelegate', {trustAs: null, valueOf: null, getTrusted: null}); }); inject(function($window, $injector) { function constructSce() { var sceProvider = new $SceProvider(); sceProvider.enabled(enabled); return $injector.invoke(sceProvider.$get, sceProvider); } if (expectException) { expect(constructSce).toThrowMinErr( '$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' + 'version < 9 in quirks mode. You can fix this by adding the text to ' + 'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' + 'information.'); } else { // no exception. constructSce(); } }); } it('should throw an exception when sce is enabled in quirks mode', function() { runTest(true, 7, true); }); it('should NOT throw an exception when sce is enabled and in standards mode', function() { runTest(true, 8, false); }); it('should NOT throw an exception when sce is enabled and documentMode is undefined', function() { runTest(true, undefined, false); }); it('should NOT throw an exception when sce is disabled even when in quirks mode', function() { runTest(false, 7, false); }); it('should NOT throw an exception when sce is disabled and in standards mode', function() { runTest(false, 8, false); }); it('should NOT throw an exception when sce is disabled and documentMode is undefined', function() { runTest(false, undefined, false); }); }); describe('when enabled', function() { it('should wrap string values with TrustedValueHolder', inject(function($sce) { var originalValue = 'original_value'; var wrappedValue = $sce.trustAs($sce.HTML, originalValue); expect(typeof wrappedValue).toBe('object'); expect($sce.getTrusted($sce.HTML, wrappedValue)).toBe('original_value'); expect(function() { $sce.getTrusted($sce.CSS, wrappedValue); }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); wrappedValue = $sce.trustAs($sce.CSS, originalValue); expect(typeof wrappedValue).toBe('object'); expect($sce.getTrusted($sce.CSS, wrappedValue)).toBe('original_value'); expect(function() { $sce.getTrusted($sce.HTML, wrappedValue); }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); wrappedValue = $sce.trustAs($sce.URL, originalValue); expect(typeof wrappedValue).toBe('object'); expect($sce.getTrusted($sce.URL, wrappedValue)).toBe('original_value'); wrappedValue = $sce.trustAs($sce.JS, originalValue); expect(typeof wrappedValue).toBe('object'); expect($sce.getTrusted($sce.JS, wrappedValue)).toBe('original_value'); })); it('should NOT wrap non-string values', inject(function($sce) { expect(function() { $sce.trustAsCss(123); }).toThrowMinErr( '$sce', 'itype', 'Attempted to trust a non-string value in a content requiring a string: ' + 'Context: css'); })); it('should NOT wrap unknown contexts', inject(function($sce) { expect(function() { $sce.trustAs('unknown1' , '123'); }).toThrowMinErr( '$sce', 'icontext', 'Attempted to trust a value in invalid context. Context: unknown1; Value: 123'); })); it('should NOT wrap undefined context', inject(function($sce) { expect(function() { $sce.trustAs(undefined, '123'); }).toThrowMinErr( '$sce', 'icontext', 'Attempted to trust a value in invalid context. Context: undefined; Value: 123'); })); it('should wrap undefined into undefined', inject(function($sce) { expect($sce.trustAsHtml(undefined)).toBe(undefined); })); it('should unwrap undefined into undefined', inject(function($sce) { expect($sce.getTrusted($sce.HTML, undefined)).toBe(undefined); })); it('should wrap null into null', inject(function($sce) { expect($sce.trustAsHtml(null)).toBe(null); })); it('should unwrap null into null', inject(function($sce) { expect($sce.getTrusted($sce.HTML, null)).toBe(null); })); it('should wrap "" into ""', inject(function($sce) { expect($sce.trustAsHtml("")).toBe(""); })); it('should unwrap "" into ""', inject(function($sce) { expect($sce.getTrusted($sce.HTML, "")).toBe(""); })); it('should unwrap values and return the original', inject(function($sce) { var originalValue = "originalValue"; var wrappedValue = $sce.trustAs($sce.HTML, originalValue); expect($sce.getTrusted($sce.HTML, wrappedValue)).toBe(originalValue); })); it('should NOT unwrap values when the type is different', inject(function($sce) { var originalValue = "originalValue"; var wrappedValue = $sce.trustAs($sce.HTML, originalValue); expect(function () { $sce.getTrusted($sce.CSS, wrappedValue); }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); it('should NOT unwrap values that had not been wrapped', inject(function($sce) { function TrustedValueHolder(trustedValue) { this.$unwrapTrustedValue = function() { return trustedValue; }; } var wrappedValue = new TrustedValueHolder("originalValue"); expect(function() { return $sce.getTrusted($sce.HTML, wrappedValue) }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); it('should implement toString on trusted values', inject(function($sce) { var originalValue = '123', wrappedValue = $sce.trustAsHtml(originalValue); expect($sce.getTrustedHtml(wrappedValue)).toBe(originalValue); expect(wrappedValue.toString()).toBe(originalValue.toString()); })); }); describe('replace $sceDelegate', function() { it('should override the default $sce.trustAs/valueOf/etc.', function() { module(function($provide) { $provide.value('$sceDelegate', { trustAs: function(type, value) { return "wrapped:" + value; }, getTrusted: function(type, value) { return "unwrapped:" + value; }, valueOf: function(value) { return "valueOf:" + value; } }); }); inject(function($sce) { expect($sce.trustAsJs("value")).toBe("wrapped:value"); expect($sce.valueOf("value")).toBe("valueOf:value"); expect($sce.getTrustedJs("value")).toBe("unwrapped:value"); expect($sce.parseAsJs("name")({name: "chirayu"})).toBe("unwrapped:chirayu"); }); }); }); describe('$sce.parseAs', function($sce) { it('should parse constant literals as trusted', inject(function($sce) { expect($sce.parseAsJs('1')()).toBe(1); expect($sce.parseAsJs('1', $sce.ANY)()).toBe(1); expect($sce.parseAsJs('1', $sce.HTML)()).toBe(1); expect($sce.parseAsJs('1', 'UNDEFINED')()).toBe(1); expect($sce.parseAsJs('true')()).toBe(true); expect($sce.parseAsJs('false')()).toBe(false); expect($sce.parseAsJs('null')()).toBe(null); expect($sce.parseAsJs('undefined')()).toBe(undefined); expect($sce.parseAsJs('"string"')()).toBe("string"); })); it('should NOT parse constant non-literals', inject(function($sce) { // Until there's a real world use case for this, we're disallowing // constant non-literals. See $SceParseProvider. var exprFn = $sce.parseAsJs('1+1'); expect(exprFn).toThrow(); })); it('should NOT return untrusted values from expression function', inject(function($sce) { var exprFn = $sce.parseAs($sce.HTML, 'foo'); expect(function() { return exprFn({}, {'foo': true}) }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); it('should NOT return trusted values of the wrong type from expression function', inject(function($sce) { var exprFn = $sce.parseAs($sce.HTML, 'foo'); expect(function() { return exprFn({}, {'foo': $sce.trustAs($sce.JS, '123')}) }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); it('should return trusted values from expression function', inject(function($sce) { var exprFn = $sce.parseAs($sce.HTML, 'foo'); expect(exprFn({}, {'foo': $sce.trustAs($sce.HTML, 'trustedValue')})).toBe('trustedValue'); })); it('should support shorthand methods', inject(function($sce) { // Test shorthand parse methods. expect($sce.parseAsHtml('1')()).toBe(1); // Test short trustAs methods. expect($sce.trustAsAny).toBeUndefined(); expect(function() { // mismatched types. $sce.parseAsCss('foo')({}, {'foo': $sce.trustAsHtml('1')}); }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); }); describe('$sceDelegate resource url policies', function() { function runTest(cfg, testFn) { return function() { module(function($sceDelegateProvider) { if (cfg.whiteList !== undefined) { $sceDelegateProvider.resourceUrlWhitelist(cfg.whiteList); } if (cfg.blackList !== undefined) { $sceDelegateProvider.resourceUrlBlacklist(cfg.blackList); } }); inject(testFn); } } it('should default to "self" which allows relative urls', runTest({}, function($sce, $document) { expect($sce.getTrustedResourceUrl('foo/bar')).toEqual('foo/bar'); })); it('should reject everything when whitelist is empty', runTest( { whiteList: [], blackList: [] }, function($sce) { expect(function() { $sce.getTrustedResourceUrl('#'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: #'); })); it('should match against normalized urls', runTest( { whiteList: [/^foo$/], blackList: [] }, function($sce) { expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); })); it('should not accept unknown matcher type', function() { expect(function() { runTest({whiteList: [{}]}, null)(); }).toThrowMinErr('$injector', 'modulerr', new RegExp( /Failed to instantiate module function ?\(\$sceDelegateProvider\) due to:\n/.source + /[^[]*\[\$sce:imatcher\] Matchers may only be "self", string patterns or RegExp objects/.source)); }); describe('adjustMatcher', function() { it('should rewrite regex into regex and add ^ & $ on either end', function() { expect(adjustMatcher(/a.*b/).exec('a.b')).not.toBeNull(); expect(adjustMatcher(/a.*b/).exec('-a.b-')).toBeNull(); // Adding ^ & $ onto a regex that already had them should also work. expect(adjustMatcher(/^a.*b$/).exec('a.b')).not.toBeNull(); expect(adjustMatcher(/^a.*b$/).exec('-a.b-')).toBeNull(); }); }); describe('regex matcher', function() { it('should support custom regex', runTest( { whiteList: [/^http:\/\/example\.com\/.*/], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); // must match entire regex expect(function() { $sce.getTrustedResourceUrl('https://example.com/foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: https://example.com/foo'); // https doesn't match (mismatched protocol.) expect(function() { $sce.getTrustedResourceUrl('https://example.com/foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: https://example.com/foo'); })); it('should match entire regex', runTest( { whiteList: [/https?:\/\/example\.com\/foo/], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); expect($sce.getTrustedResourceUrl('https://example.com/foo')).toEqual('https://example.com/foo'); expect(function() { $sce.getTrustedResourceUrl('http://example.com/fo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/fo'); // Suffix not allowed even though original regex does not contain an ending $. expect(function() { $sce.getTrustedResourceUrl('http://example.com/foo2'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/foo2'); // Prefix not allowed even though original regex does not contain a leading ^. expect(function() { $sce.getTrustedResourceUrl('xhttp://example.com/foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: xhttp://example.com/foo'); })); }); describe('string matchers', function() { it('should support strings as matchers', runTest( { whiteList: ['http://example.com/foo'], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); // "." is not a special character like in a regex. expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo'); // You can match a prefix. expect(function() { $sce.getTrustedResourceUrl('http://example.com/foo2'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/foo2'); // You can match a suffix. expect(function() { $sce.getTrustedResourceUrl('xhttp://example.com/foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: xhttp://example.com/foo'); })); it('should support the * wildcard', runTest( { whiteList: ['http://example.com/foo*'], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); // The * wildcard should match extra characters. expect($sce.getTrustedResourceUrl('http://example.com/foo-bar')).toEqual('http://example.com/foo-bar'); // The * wildcard does not match ':' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo:bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo:bar'); // The * wildcard does not match '/' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo/bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo/bar'); // The * wildcard does not match '.' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo.bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo.bar'); // The * wildcard does not match '?' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo?bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo?bar'); // The * wildcard does not match '&' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo&bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo&bar'); // The * wildcard does not match ';' expect(function() { $sce.getTrustedResourceUrl('http://example-com/foo;bar'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example-com/foo;bar'); })); it('should support the ** wildcard', runTest( { whiteList: ['http://example.com/foo**'], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); // The ** wildcard should match extra characters. expect($sce.getTrustedResourceUrl('http://example.com/foo-bar')).toEqual('http://example.com/foo-bar'); // The ** wildcard accepts the ':/.?&' characters. expect($sce.getTrustedResourceUrl('http://example.com/foo:1/2.3?4&5-6')).toEqual('http://example.com/foo:1/2.3?4&5-6'); })); it('should not accept *** in the string', function() { expect(function() { runTest({whiteList: ['http://***']}, null)(); }).toThrowMinErr('$injector', 'modulerr', new RegExp( /Failed to instantiate module function ?\(\$sceDelegateProvider\) due to:\n/.source + /[^[]*\[\$sce:iwcard\] Illegal sequence \*\*\* in string matcher\. String: http:\/\/\*\*\*/.source)); }); }); describe('"self" matcher', function() { it('should support the special string "self" in whitelist', runTest( { whiteList: ['self'], blackList: [] }, function($sce) { expect($sce.getTrustedResourceUrl('foo')).toEqual('foo'); })); it('should support the special string "self" in blacklist', runTest( { whiteList: [/.*/], blackList: ['self'] }, function($sce) { expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); })); }); it('should have blacklist override the whitelist', runTest( { whiteList: ['self'], blackList: ['self'] }, function($sce) { expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); })); it('should support multiple items in both lists', runTest( { whiteList: [/^http:\/\/example.com\/1$/, /^http:\/\/example.com\/2$/, /^http:\/\/example.com\/3$/, 'self'], blackList: [/^http:\/\/example.com\/3$/, /.*\/open_redirect/], }, function($sce) { expect($sce.getTrustedResourceUrl('same_domain')).toEqual('same_domain'); expect($sce.getTrustedResourceUrl('http://example.com/1')).toEqual('http://example.com/1'); expect($sce.getTrustedResourceUrl('http://example.com/2')).toEqual('http://example.com/2'); expect(function() { $sce.getTrustedResourceUrl('http://example.com/3'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/3'); expect(function() { $sce.getTrustedResourceUrl('open_redirect'); }).toThrowMinErr( '$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: open_redirect'); })); }); describe('sanitizing html', function() { describe('when $sanitize is NOT available', function() { it('should throw an exception for getTrusted(string) values', inject(function($sce) { expect(function() { $sce.getTrustedHtml(''); }).toThrowMinErr( '$sce', 'unsafe', 'Attempting to use an unsafe value in a safe context.'); })); }); describe('when $sanitize is available', function() { beforeEach(function() { module('ngSanitize'); }); it('should sanitize html using $sanitize', inject(function($sce) { expect($sce.getTrustedHtml('abc')).toBe('abc'); })); }); }); }); angular.js-1.2.11/test/ng/snifferSpec.js000066400000000000000000000230441227375216300200430ustar00rootroot00000000000000'use strict'; describe('$sniffer', function() { function sniffer($window, $document) { $window.navigator = {}; $document = jqLite($document || {}); if (!$document[0].body) { $document[0].body = window.document.body; } return new $SnifferProvider().$get[2]($window, $document); } describe('history', function() { it('should be true if history.pushState defined', function() { expect(sniffer({history: {pushState: noop, replaceState: noop}}).history).toBe(true); }); it('should be false if history or pushState not defined', function() { expect(sniffer({history: {}}).history).toBe(false); expect(sniffer({}).history).toBe(false); }); }); describe('hashchange', function() { it('should be true if onhashchange property defined', function() { expect(sniffer({onhashchange: true}).hashchange).toBe(true); }); it('should be false if onhashchange property not defined', function() { expect(sniffer({}).hashchange).toBe(false); }); it('should be false if documentMode is 7 (IE8 comp mode)', function() { expect(sniffer({onhashchange: true}, {documentMode: 7}).hashchange).toBe(false); }); }); describe('hasEvent', function() { var mockDocument, mockDivElement, $sniffer; beforeEach(function() { mockDocument = {createElement: jasmine.createSpy('createElement')}; mockDocument.createElement.andCallFake(function(elm) { if (elm === 'div') return mockDivElement; }); $sniffer = sniffer({}, mockDocument); }); it('should return true if "onchange" is present in a div element', function() { mockDivElement = {onchange: noop}; expect($sniffer.hasEvent('change')).toBe(true); }); it('should return false if "oninput" is not present in a div element', function() { mockDivElement = {}; expect($sniffer.hasEvent('input')).toBe(false); }); it('should only create the element once', function() { mockDivElement = {}; $sniffer.hasEvent('change'); $sniffer.hasEvent('change'); $sniffer.hasEvent('change'); expect(mockDocument.createElement).toHaveBeenCalledOnce(); }); it('should claim that IE9 doesn\'t have support for "oninput"', function() { // IE9 implementation is fubared, so it's better to pretend that it doesn't have the support mockDivElement = {oninput: noop}; expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true); }); }); describe('csp', function() { it('should be false by default', function() { expect(sniffer({}).csp).toBe(false); }); }); describe('vendorPrefix', function() { it('should return the correct vendor prefix based on the browser', function() { inject(function($sniffer, $window) { var expectedPrefix; var ua = $window.navigator.userAgent.toLowerCase(); if(/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) { expectedPrefix = 'Webkit'; } else if(/firefox/i.test(ua)) { expectedPrefix = 'Moz'; } else if(/ie/i.test(ua) || /trident/i.test(ua)) { expectedPrefix = 'Ms'; } else if(/opera/i.test(ua)) { expectedPrefix = 'O'; } expect($sniffer.vendorPrefix).toBe(expectedPrefix); }); }); it('should still work for an older version of Webkit', function() { module(function($provide) { var doc = { body : { style : { WebkitOpacity: '0' } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.vendorPrefix).toBe('webkit'); }); }); }); describe('animations', function() { it('should be either true or false', function() { inject(function($sniffer) { expect($sniffer.animations).not.toBe(undefined); }); }); it('should be false when there is no animation style', function() { module(function($provide) { var doc = { body : { style : {} } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.animations).toBe(false); }); }); it('should be true with vendor-specific animations', function() { module(function($provide) { var animationStyle = 'some_animation 2s linear'; var doc = { body : { style : { WebkitAnimation : animationStyle, MozAnimation : animationStyle, OAnimation : animationStyle } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.animations).toBe(true); }); }); it('should be true with w3c-style animations', function() { module(function($provide) { var doc = { body : { style : { animation : 'some_animation 2s linear' } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.animations).toBe(true); }); }); it('should be true on android with older body style properties', function() { module(function($provide) { var doc = { body : { style : { webkitAnimation: '' } } }; var win = { navigator: { userAgent: 'android 2' } }; $provide.value('$document', jqLite(doc)); $provide.value('$window', win); }); inject(function($sniffer) { expect($sniffer.animations).toBe(true); }); }); it('should be true when an older version of Webkit is used', function() { module(function($provide) { var doc = { body : { style : { WebkitOpacity: '0' } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.animations).toBe(false); }); }); }); describe('transitions', function() { it('should be either true or false', function() { inject(function($sniffer) { expect($sniffer.transitions).not.toBe(undefined); }); }); it('should be false when there is no transition style', function() { module(function($provide) { var doc = { body : { style : {} } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.transitions).toBe(false); }); }); it('should be true with vendor-specific transitions', function() { module(function($provide) { var transitionStyle = '1s linear all'; var doc = { body : { style : { WebkitTransition : transitionStyle, MozTransition : transitionStyle, OTransition : transitionStyle } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.transitions).toBe(true); }); }); it('should be true with w3c-style transitions', function() { module(function($provide) { var doc = { body : { style : { transition : '1s linear all' } } }; $provide.value('$document', jqLite(doc)); }); inject(function($sniffer) { expect($sniffer.transitions).toBe(true); }); }); it('should be true on android with older body style properties', function() { module(function($provide) { var doc = { body : { style : { webkitTransition: '' } } }; var win = { navigator: { userAgent: 'android 2' } }; $provide.value('$document', jqLite(doc)); $provide.value('$window', win); }); inject(function($sniffer) { expect($sniffer.transitions).toBe(true); }); }); }); describe('history', function() { it('should be true on Boxee box with an older version of Webkit', function() { module(function($provide) { var doc = { body : { style : {} } }; var win = { history: { pushState: noop }, navigator: { userAgent: 'boxee (alpha/Darwin 8.7.1 i386 - 0.9.11.5591)' } }; $provide.value('$document', jqLite(doc)); $provide.value('$window', win); }); inject(function($sniffer) { expect($sniffer.history).toBe(false); }); }); }); it('should provide the android version', function() { module(function($provide) { var win = { navigator: { userAgent: 'android 2' } }; $provide.value('$document', jqLite({})); $provide.value('$window', win); }); inject(function($sniffer) { expect($sniffer.android).toBe(2); }); }); it('should return the internal msie flag', inject(function($sniffer) { expect(isNaN($sniffer.msie)).toBe(isNaN(msie)); if (msie) { expect($sniffer.msie).toBe(msie); } })); it('should return document.documentMode as msieDocumentMode', function() { var someDocumentMode = 123; expect(sniffer({}, {documentMode: someDocumentMode}).msieDocumentMode).toBe(someDocumentMode); }); }); angular.js-1.2.11/test/ng/timeoutSpec.js000066400000000000000000000152721227375216300201010ustar00rootroot00000000000000'use strict'; describe('$timeout', function() { beforeEach(module(provideLog)); it('should delegate functions to $browser.defer', inject(function($timeout, $browser) { var counter = 0; $timeout(function() { counter++; }); expect(counter).toBe(0); $browser.defer.flush(); expect(counter).toBe(1); expect(function() {$browser.defer.flush();}).toThrow('No deferred tasks to be flushed'); expect(counter).toBe(1); })); it('should call $apply after each callback is executed', inject(function($timeout, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $timeout(function() {}); expect(applySpy).not.toHaveBeenCalled(); $timeout.flush(); expect(applySpy).toHaveBeenCalledOnce(); applySpy.reset(); $timeout(function() {}); $timeout(function() {}); $timeout.flush(); expect(applySpy.callCount).toBe(2); })); it('should NOT call $apply if skipApply is set to true', inject(function($timeout, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $timeout(function() {}, 12, false); expect(applySpy).not.toHaveBeenCalled(); $timeout.flush(); expect(applySpy).not.toHaveBeenCalled(); })); it('should allow you to specify the delay time', inject(function($timeout, $browser) { var defer = spyOn($browser, 'defer'); $timeout(noop, 123); expect(defer.callCount).toEqual(1); expect(defer.mostRecentCall.args[1]).toEqual(123); })); it('should return a promise which will be resolved with return value of the timeout callback', inject(function($timeout, log) { var promise = $timeout(function() { log('timeout'); return 'buba'; }); promise.then(function(value) { log('promise success: ' + value); }, log.fn('promise error')); expect(log).toEqual([]); $timeout.flush(); expect(log).toEqual(['timeout', 'promise success: buba']); })); it('should forget references to deferreds when callback called even if skipApply is true', inject(function($timeout, $browser) { // $browser.defer.cancel is only called on cancel if the deferred object is still referenced var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough(); var promise1 = $timeout(function() {}, 0, false); var promise2 = $timeout(function() {}, 100, false); expect(cancelSpy).not.toHaveBeenCalled(); $timeout.flush(0); // Promise1 deferred object should already be removed from the list and not cancellable $timeout.cancel(promise1); expect(cancelSpy).not.toHaveBeenCalled(); // Promise2 deferred object should not have been called and should be cancellable $timeout.cancel(promise2); expect(cancelSpy).toHaveBeenCalled(); })); describe('exception handling', function() { beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); it('should delegate exception to the $exceptionHandler service', inject( function($timeout, $exceptionHandler) { $timeout(function() {throw "Test Error";}); expect($exceptionHandler.errors).toEqual([]); $timeout.flush(); expect($exceptionHandler.errors).toEqual(["Test Error"]); })); it('should call $apply even if an exception is thrown in callback', inject( function($timeout, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $timeout(function() {throw "Test Error";}); expect(applySpy).not.toHaveBeenCalled(); $timeout.flush(); expect(applySpy).toHaveBeenCalled(); })); it('should reject the timeout promise when an exception is thrown in the timeout callback', inject(function($timeout, log) { var promise = $timeout(function() { throw "Some Error"; }); promise.then(log.fn('success'), function(reason) { log('error: ' + reason); }); $timeout.flush(); expect(log).toEqual('error: Some Error'); })); it('should forget references to relevant deferred even when exception is thrown', inject(function($timeout, $browser) { // $browser.defer.cancel is only called on cancel if the deferred object is still referenced var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough(); var promise = $timeout(function() { throw "Test Error"; }, 0, false); $timeout.flush(); expect(cancelSpy).not.toHaveBeenCalled(); $timeout.cancel(promise); expect(cancelSpy).not.toHaveBeenCalled(); })); }); describe('cancel', function() { it('should cancel tasks', inject(function($timeout) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), task3 = jasmine.createSpy('task3'), promise1, promise3; promise1 = $timeout(task1); $timeout(task2); promise3 = $timeout(task3, 333); $timeout.cancel(promise3); $timeout.cancel(promise1); $timeout.flush(); expect(task1).not.toHaveBeenCalled(); expect(task2).toHaveBeenCalledOnce(); expect(task3).not.toHaveBeenCalled(); })); it('should cancel the promise', inject(function($timeout, log) { var promise = $timeout(noop); promise.then(function(value) { log('promise success: ' + value); }, function(err) { log('promise error: ' + err); }, function(note) { log('promise update: ' + note); }); expect(log).toEqual([]); $timeout.cancel(promise); $timeout.flush(); expect(log).toEqual(['promise error: canceled']); })); it('should return true if a task was successfully canceled', inject(function($timeout) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), promise1, promise2; promise1 = $timeout(task1); $timeout.flush(); promise2 = $timeout(task2); expect($timeout.cancel(promise1)).toBe(false); expect($timeout.cancel(promise2)).toBe(true); })); it('should not throw a runtime exception when given an undefined promise', inject(function($timeout) { expect($timeout.cancel()).toBe(false); })); it('should forget references to relevant deferred', inject(function($timeout, $browser) { // $browser.defer.cancel is only called on cancel if the deferred object is still referenced var cancelSpy = spyOn($browser.defer, 'cancel').andCallThrough(); var promise = $timeout(function() {}, 0, false); expect(cancelSpy).not.toHaveBeenCalled(); $timeout.cancel(promise); expect(cancelSpy).toHaveBeenCalledOnce(); // Promise deferred object should already be removed from the list and not cancellable again $timeout.cancel(promise); expect(cancelSpy).toHaveBeenCalledOnce(); })); }); }); angular.js-1.2.11/test/ng/urlUtilsSpec.js000066400000000000000000000033071227375216300202320ustar00rootroot00000000000000'use strict'; describe('urlUtils', function() { describe('urlResolve', function() { it('should normalize a relative url', function () { expect(urlResolve("foo").href).toMatch(/^https?:\/\/[^/]+\/foo$/); }); it('should parse relative URL into component pieces', function () { var parsed = urlResolve("foo"); expect(parsed.href).toMatch(/https?:\/\//); expect(parsed.protocol).toMatch(/^https?/); expect(parsed.host).not.toBe(""); expect(parsed.hostname).not.toBe(""); expect(parsed.pathname).not.toBe(""); }); it('should return pathname as / if empty path provided', function () { //IE counts / as empty, necessary to use / so that pathname is not context.html var parsed = urlResolve('/'); expect(parsed.pathname).toBe('/'); }) }); describe('isSameOrigin', function() { it('should support various combinations of urls - both string and parsed', inject(function($document) { function expectIsSameOrigin(url, expectedValue) { expect(urlIsSameOrigin(url)).toBe(expectedValue); expect(urlIsSameOrigin(urlResolve(url, true))).toBe(expectedValue); } expectIsSameOrigin('path', true); var origin = urlResolve($document[0].location.href, true); expectIsSameOrigin('//' + origin.host + '/path', true); // Different domain. expectIsSameOrigin('http://example.com/path', false); // Auto fill protocol. expectIsSameOrigin('//example.com/path', false); // Should not match when the ports are different. // This assumes that the test is *not* running on port 22 (very unlikely). expectIsSameOrigin('//' + origin.hostname + ':22/path', false); })); }); }); angular.js-1.2.11/test/ng/windowSpec.js000066400000000000000000000002261227375216300177130ustar00rootroot00000000000000'use strict'; describe('$window', function() { it("should inject $window", inject(function($window) { expect($window).toBe(window); })); }); angular.js-1.2.11/test/ngAnimate/000077500000000000000000000000001227375216300165325ustar00rootroot00000000000000angular.js-1.2.11/test/ngAnimate/animateSpec.js000066400000000000000000003502411227375216300213260ustar00rootroot00000000000000'use strict'; describe("ngAnimate", function() { beforeEach(module('ngAnimate')); it("should disable animations on bootstrap for structural animations even after the first digest has passed", function() { var hasBeenAnimated = false; module(function($animateProvider) { $animateProvider.register('.my-structrual-animation', function() { return { enter : function(element, done) { hasBeenAnimated = true; done(); }, leave : function(element, done) { hasBeenAnimated = true; done(); } } }); }); inject(function($rootScope, $compile, $animate, $rootElement, $document) { var element = $compile('
                      ...
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.enter(element, $rootElement); $rootScope.$digest(); expect(hasBeenAnimated).toBe(false); $animate.leave(element); $rootScope.$digest(); expect(hasBeenAnimated).toBe(true); }); }); //we use another describe block because the before/after operations below //are used across all animations tests and we don't want that same behavior //to be used on the root describe block at the start of the animateSpec.js file describe('', function() { var ss, body; beforeEach(module(function() { body = jqLite(document.body); return function($window, $document, $animate, $timeout, $rootScope) { ss = createMockStyleSheet($document, $window); try { $timeout.flush(); } catch(e) {} $animate.enabled(true); $rootScope.$digest(); }; })); afterEach(function(){ if(ss) { ss.destroy(); } dealoc(body); }); describe("$animate", function() { var element, $rootElement; function html(html) { body.append($rootElement); $rootElement.html(html); element = $rootElement.children().eq(0); return element; } describe("enable / disable", function() { it("should work for all animations", inject(function($animate) { expect($animate.enabled()).toBe(true); expect($animate.enabled(0)).toBe(false); expect($animate.enabled()).toBe(false); expect($animate.enabled(1)).toBe(true); expect($animate.enabled()).toBe(true); })); it('should place a hard disable on all child animations', function() { var count = 0; module(function($animateProvider) { $animateProvider.register('.animated', function() { return { addClass : function(element, className, done) { count++; done(); } } }); }); inject(function($compile, $rootScope, $animate, $sniffer, $rootElement, $timeout) { $animate.enabled(true); var elm1 = $compile('
                      ')($rootScope); var elm2 = $compile('
                      ')($rootScope); $rootElement.append(elm1); angular.element(document.body).append($rootElement); $animate.addClass(elm1, 'klass'); expect(count).toBe(1); $animate.enabled(false); $animate.addClass(elm1, 'klass2'); expect(count).toBe(1); $animate.enabled(true); elm1.append(elm2); $animate.addClass(elm2, 'klass'); expect(count).toBe(2); $animate.enabled(false, elm1); $animate.addClass(elm2, 'klass2'); expect(count).toBe(2); var root = angular.element($rootElement[0]); $rootElement.addClass('animated'); $animate.addClass(root, 'klass2'); expect(count).toBe(3); }); }); it('should skip animations if the element is attached to the $rootElement', function() { var count = 0; module(function($animateProvider) { $animateProvider.register('.animated', function() { return { addClass : function(element, className, done) { count++; done(); } } }); }); inject(function($compile, $rootScope, $animate, $sniffer, $rootElement, $timeout) { $animate.enabled(true); var elm1 = $compile('
                      ')($rootScope); $animate.addClass(elm1, 'klass2'); expect(count).toBe(0); }); }); it('should check enable/disable animations up until the $rootElement element', function() { var rootElm = jqLite('
                      '); var captured = false; module(function($provide, $animateProvider) { $provide.value('$rootElement', rootElm); $animateProvider.register('.capture-animation', function() { return { addClass : function(element, className, done) { captured = true; done(); } } }); }); inject(function($animate, $rootElement, $rootScope, $compile, $timeout) { var initialState; angular.bootstrap(rootElm, ['ngAnimate']); $animate.enabled(true); var element = $compile('
                      ')($rootScope); rootElm.append(element); expect(captured).toBe(false); $animate.addClass(element, 'red'); expect(captured).toBe(true); captured = false; $animate.enabled(false); $animate.addClass(element, 'blue'); expect(captured).toBe(false); //clean up the mess $animate.enabled(false, rootElm); dealoc(rootElm); }); }); }); describe("with polyfill", function() { var child, after; beforeEach(function() { module(function($animateProvider) { $animateProvider.register('.custom', function() { return { start: function(element, done) { done(); } } }); $animateProvider.register('.custom-delay', function($timeout) { function animate(element, done) { done = arguments.length == 3 ? arguments[2] : done; $timeout(done, 2000, false); return function() { element.addClass('animation-cancelled'); } } return { leave : animate, addClass : animate, removeClass : animate } }); $animateProvider.register('.custom-long-delay', function($timeout) { function animate(element, done) { done = arguments.length == 3 ? arguments[2] : done; $timeout(done, 20000, false); return function(cancelled) { element.addClass(cancelled ? 'animation-cancelled' : 'animation-ended'); } } return { leave : animate, addClass : animate, removeClass : animate } }); $animateProvider.register('.setup-memo', function() { return { removeClass: function(element, className, done) { element.text('memento'); done(); } } }); return function($animate, $compile, $rootScope, $rootElement) { element = $compile('
                      ')($rootScope); forEach(['.ng-hide-add', '.ng-hide-remove', '.ng-enter', '.ng-leave', '.ng-move'], function(selector) { ss.addRule(selector, '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); }); child = $compile('
                      ...
                      ')($rootScope); jqLite($document[0].body).append($rootElement); element.append(child); after = $compile('
                      ')($rootScope); $rootElement.append(element); }; }); }) it("should animate the enter animation event", inject(function($animate, $rootScope, $sniffer, $timeout) { element[0].removeChild(child[0]); expect(element.contents().length).toBe(0); $animate.enter(child, element); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(element.contents().length).toBe(1); })); it("should animate the leave animation event", inject(function($animate, $rootScope, $sniffer, $timeout) { expect(element.contents().length).toBe(1); $animate.leave(child); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-leave')).toBe(true); expect(child.hasClass('ng-leave-active')).toBe(true); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(element.contents().length).toBe(0); })); it("should animate the move animation event", inject(function($animate, $compile, $rootScope, $timeout, $sniffer) { $rootScope.$digest(); element.empty(); var child1 = $compile('
                      1
                      ')($rootScope); var child2 = $compile('
                      2
                      ')($rootScope); element.append(child1); element.append(child2); expect(element.text()).toBe('12'); $animate.move(child1, element, child2); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); } expect(element.text()).toBe('21'); })); it("should animate the show animation event", inject(function($animate, $rootScope, $sniffer, $timeout) { $rootScope.$digest(); child.addClass('ng-hide'); expect(child).toBeHidden(); $animate.removeClass(child, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-hide-remove')).toBe(true); expect(child.hasClass('ng-hide-remove-active')).toBe(true); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(child.hasClass('ng-hide-remove')).toBe(false); expect(child.hasClass('ng-hide-remove-active')).toBe(false); expect(child).toBeShown(); })); it("should animate the hide animation event", inject(function($animate, $rootScope, $sniffer, $timeout) { $rootScope.$digest(); expect(child).toBeShown(); $animate.addClass(child, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-hide-add')).toBe(true); expect(child.hasClass('ng-hide-add-active')).toBe(true); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(child).toBeHidden(); })); it("should assign the ng-event className to all animation events when transitions/keyframes are used", inject(function($animate, $sniffer, $rootScope, $timeout) { if (!$sniffer.transitions) return; $rootScope.$digest(); element[0].removeChild(child[0]); //enter $animate.enter(child, element); $rootScope.$digest(); $animate.triggerReflow(); expect(child.attr('class')).toContain('ng-enter'); expect(child.attr('class')).toContain('ng-enter-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); $timeout.flush(); //move element.append(after); $animate.move(child, element, after); $rootScope.$digest(); $animate.triggerReflow(); expect(child.attr('class')).toContain('ng-move'); expect(child.attr('class')).toContain('ng-move-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); $timeout.flush(); //hide $animate.addClass(child, 'ng-hide'); $animate.triggerReflow(); expect(child.attr('class')).toContain('ng-hide-add'); expect(child.attr('class')).toContain('ng-hide-add-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); //show $animate.removeClass(child, 'ng-hide'); $animate.triggerReflow(); expect(child.attr('class')).toContain('ng-hide-remove'); expect(child.attr('class')).toContain('ng-hide-remove-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); //leave $animate.leave(child); $rootScope.$digest(); $animate.triggerReflow(); expect(child.attr('class')).toContain('ng-leave'); expect(child.attr('class')).toContain('ng-leave-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); })); it("should not run if animations are disabled", inject(function($animate, $rootScope, $timeout, $sniffer) { $animate.enabled(false); $rootScope.$digest(); element.addClass('setup-memo'); element.text('123'); expect(element.text()).toBe('123'); $animate.removeClass(element, 'ng-hide'); expect(element.text()).toBe('123'); $animate.enabled(true); element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); } expect(element.text()).toBe('memento'); })); it("should only call done() once and right away if another animation takes place in between", inject(function($animate, $rootScope, $sniffer, $timeout) { element.append(child); child.addClass('custom-delay'); expect(element).toBeShown(); $animate.addClass(child, 'ng-hide'); if($sniffer.transitions) { expect(child).toBeShown(); } $animate.leave(child); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); } expect(child).toBeHidden(); //hides instantly //lets change this to prove that done doesn't fire anymore for the previous hide() operation child.css('display','block'); child.removeClass('ng-hide'); if($sniffer.transitions) { expect(element.children().length).toBe(1); //still animating browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } $timeout.flush(2000); $timeout.flush(2000); expect(child).toBeShown(); expect(element.children().length).toBe(0); })); it("should retain existing styles of the animated element", inject(function($animate, $rootScope, $sniffer, $timeout) { element.append(child); child.attr('style', 'width: 20px'); $animate.addClass(child, 'ng-hide'); $animate.leave(child); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); //this is to verify that the existing style is appended with a semicolon automatically expect(child.attr('style')).toMatch(/width: 20px;.+?/i); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(child.attr('style')).toMatch(/width: 20px/i); })); it("should call the cancel callback when another animation is called on the same element", inject(function($animate, $rootScope, $sniffer, $timeout) { element.append(child); child.addClass('custom-delay ng-hide'); $animate.removeClass(child, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } $timeout.flush(2000); $animate.addClass(child, 'ng-hide'); expect(child.hasClass('animation-cancelled')).toBe(true); })); it("should skip a class-based animation if the same element already has an ongoing structural animation", inject(function($animate, $rootScope, $sniffer, $timeout) { var completed = false; $animate.enter(child, element, null, function() { completed = true; }); $rootScope.$digest(); expect(completed).toBe(false); $animate.addClass(child, 'green'); expect(element.hasClass('green')); expect(completed).toBe(false); if($sniffer.transitions) { $animate.triggerReflow(); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } $timeout.flush(); expect(completed).toBe(true); })); it("should skip class-based animations if animations are directly disabled on the same element", function() { var capture; module(function($animateProvider) { $animateProvider.register('.capture', function() { return { addClass : function(element, className, done) { capture = true; done(); } }; }); }); inject(function($animate, $rootScope, $sniffer, $timeout) { $animate.enabled(true); $animate.enabled(false, element); $animate.addClass(element, 'capture'); expect(element.hasClass('capture')).toBe(true); expect(capture).not.toBe(true); }); }); it("should fire the cancel/end function with the correct flag in the parameters", inject(function($animate, $rootScope, $sniffer, $timeout) { element.append(child); $animate.addClass(child, 'custom-delay'); $animate.addClass(child, 'custom-long-delay'); expect(child.hasClass('animation-cancelled')).toBe(true); expect(child.hasClass('animation-ended')).toBe(false); $timeout.flush(); expect(child.hasClass('animation-ended')).toBe(true); })); it("should NOT clobber all data on an element when animation is finished", inject(function($animate) { child.css('display','none'); element.data('foo', 'bar'); $animate.removeClass(element, 'ng-hide'); $animate.addClass(element, 'ng-hide'); expect(element.data('foo')).toEqual('bar'); })); it("should allow multiple JS animations which run in parallel", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { $animate.addClass(element, 'custom-delay custom-long-delay'); $timeout.flush(2000); $timeout.flush(20000); expect(element.hasClass('custom-delay')).toBe(true); expect(element.hasClass('custom-long-delay')).toBe(true); })); it("should allow both multiple JS and CSS animations which run in parallel", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, _$rootElement_) { $rootElement = _$rootElement_; ss.addRule('.ng-hide-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.ng-hide-remove', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('custom-delay custom-long-delay'); $rootScope.$digest(); $animate.removeClass(element, 'ng-hide'); if($sniffer.transitions) { browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } $timeout.flush(2000); $timeout.flush(20000); expect(element.hasClass('custom-delay')).toBe(true); expect(element.hasClass('custom-delay-add')).toBe(false); expect(element.hasClass('custom-delay-add-active')).toBe(false); expect(element.hasClass('custom-long-delay')).toBe(true); expect(element.hasClass('custom-long-delay-add')).toBe(false); expect(element.hasClass('custom-long-delay-add-active')).toBe(false); })); }); describe("with CSS3", function() { beforeEach(function() { module(function() { return function(_$rootElement_) { $rootElement = _$rootElement_; }; }) }); describe("Animations", function() { it("should properly detect and make use of CSS Animations", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { ss.addRule('.ng-hide-add', '-webkit-animation: some_animation 4s linear 0s 1 alternate;' + 'animation: some_animation 4s linear 0s 1 alternate;'); ss.addRule('.ng-hide-remove', '-webkit-animation: some_animation 4s linear 0s 1 alternate;' + 'animation: some_animation 4s linear 0s 1 alternate;'); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.animations) { $animate.triggerReflow(); browserTrigger(element,'animationend', { timeStamp: Date.now() + 4000, elapsedTime: 4 }); } expect(element).toBeShown(); })); it("should properly detect and make use of CSS Animations with multiple iterations", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-animation-duration: 2s;' + '-webkit-animation-iteration-count: 3;' + 'animation-duration: 2s;' + 'animation-iteration-count: 3;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.animations) { $animate.triggerReflow(); browserTrigger(element,'animationend', { timeStamp: Date.now() + 6000, elapsedTime: 6 }); } expect(element).toBeShown(); })); it("should not consider the animation delay is provided", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-animation-duration: 2s;' + '-webkit-animation-delay: 10s;' + '-webkit-animation-iteration-count: 5;' + 'animation-duration: 2s;' + 'animation-delay: 10s;' + 'animation-iteration-count: 5;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { $animate.triggerReflow(); browserTrigger(element,'animationend', { timeStamp : Date.now() + 20000, elapsedTime: 10 }); } expect(element).toBeShown(); })); it("should skip animations if disabled and run when enabled", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { $animate.enabled(false); var style = '-webkit-animation: some_animation 2s linear 0s 1 alternate;' + 'animation: some_animation 2s linear 0s 1 alternate;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); expect(element).toBeShown(); })); it("should finish the previous animation when a new animation is started", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-animation: some_animation 2s linear 0s 1 alternate;' + 'animation: some_animation 2s linear 0s 1 alternate;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('custom'); $animate.removeClass(element, 'ng-hide'); if($sniffer.animations) { $animate.triggerReflow(); expect(element.hasClass('ng-hide-remove')).toBe(true); expect(element.hasClass('ng-hide-remove-active')).toBe(true); } element.removeClass('ng-hide'); $animate.addClass(element, 'ng-hide'); expect(element.hasClass('ng-hide-remove')).toBe(false); //added right away if($sniffer.animations) { //cleanup some pending animations $animate.triggerReflow(); expect(element.hasClass('ng-hide-add')).toBe(true); expect(element.hasClass('ng-hide-add-active')).toBe(true); browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000, elapsedTime: 2 }); } expect(element.hasClass('ng-hide-remove-active')).toBe(false); })); it("should stagger the items when the correct CSS class is provided", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { if(!$sniffer.animations) return; $animate.enabled(true); ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave, .real-animation-fake.ng-enter, .real-animation-fake.ng-leave', '-webkit-animation:1s my_animation;' + 'animation:1s my_animation;'); ss.addRule('.real-animation.ng-enter-stagger, .real-animation.ng-leave-stagger', '-webkit-animation-delay:0.1s;' + '-webkit-animation-duration:0s;' + 'animation-delay:0.1s;' + 'animation-duration:0s;'); ss.addRule('.fake-animation.ng-enter-stagger, .fake-animation.ng-leave-stagger', '-webkit-animation-delay:0.1s;' + '-webkit-animation-duration:1s;' + 'animation-delay:0.1s;' + 'animation-duration:1s;'); var container = $compile(html('
                      '))($rootScope); var elements = []; for(var i = 0; i < 5; i++) { var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements.push(element); }; $rootScope.$digest(); $animate.triggerReflow(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).toMatch(/animation-delay: 0\.1\d*s/); expect(elements[2].attr('style')).toMatch(/animation-delay: 0\.2\d*s/); expect(elements[3].attr('style')).toMatch(/animation-delay: 0\.3\d*s/); expect(elements[4].attr('style')).toMatch(/animation-delay: 0\.4\d*s/); for(var i = 0; i < 5; i++) { dealoc(elements[i]); var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements[i] = element; }; $rootScope.$digest(); var expectFailure = true; try { $animate.triggerReflow(); expectFailure = false; } catch(e) {} expect(expectFailure).toBe(true); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).not.toMatch(/animation-delay: 0\.1\d*s/); expect(elements[2].attr('style')).not.toMatch(/animation-delay: 0\.2\d*s/); expect(elements[3].attr('style')).not.toMatch(/animation-delay: 0\.3\d*s/); expect(elements[4].attr('style')).not.toMatch(/animation-delay: 0\.4\d*s/); })); it("should stagger items when multiple animation durations/delays are defined", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { if(!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.stagger-animation.ng-enter, .stagger-animation.ng-leave', '-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' + 'animation:my_animation 1s 1s, your_animation 1s 2s;'); ss.addRule('.stagger-animation.ng-enter-stagger, .stagger-animation.ng-leave-stagger', '-webkit-animation-delay:0.1s;' + 'animation-delay:0.1s;'); var container = $compile(html('
                      '))($rootScope); var elements = []; for(var i = 0; i < 4; i++) { var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements.push(element); }; $rootScope.$digest(); $animate.triggerReflow(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.1\d*s,\s*2\.1\d*s/); expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/); expect(elements[3].attr('style')).toMatch(/animation-delay: 1\.3\d*s,\s*2\.3\d*s/); })); }); describe("Transitions", function() { it("should skip transitions if disabled and run when enabled", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-transition: 1s linear all;' + 'transition: 1s linear all;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); $animate.enabled(false); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); expect(element).toBeShown(); $animate.enabled(true); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { $animate.triggerReflow(); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(element).toBeShown(); })); it("should skip animations if disabled and run when enabled picking the longest specified duration", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-transition-duration: 1s, 2000ms, 1s;' + '-webkit-transition-property: height, left, opacity;' + 'transition-duration: 1s, 2000ms, 1s;' + 'transition-property: height, left, opacity;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      foo
                      '))($rootScope); element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { $animate.triggerReflow(); var now = Date.now(); browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2 }); expect(element.hasClass('ng-animate')).toBe(false); } expect(element).toBeShown(); })); it("should skip animations if disabled and run when enabled picking the longest specified duration/delay combination", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { $animate.enabled(false); var style = '-webkit-transition-duration: 1s, 0s, 1s; ' + '-webkit-transition-delay: 2s, 1000ms, 2s; ' + '-webkit-transition-property: height, left, opacity;' + 'transition-duration: 1s, 0s, 1s; ' + 'transition-delay: 2s, 1000ms, 2s; ' + 'transition-property: height, left, opacity;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      foo
                      '))($rootScope); element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); expect(element).toBeShown(); $animate.enabled(true); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { $animate.triggerReflow(); var now = Date.now(); browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); browserTrigger(element,'transitionend', { timeStamp: now + 3000, elapsedTime: 3 }); browserTrigger(element,'transitionend', { timeStamp: now + 3000, elapsedTime: 3 }); } expect(element).toBeShown(); })); it("should NOT overwrite styles with outdated values when animation completes", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { if(!$sniffer.transitions) return; var style = '-webkit-transition-duration: 1s, 2000ms, 1s;' + '-webkit-transition-property: height, left, opacity;' + 'transition-duration: 1s, 2000ms, 1s;' + 'transition-property: height, left, opacity;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      foo
                      '))($rootScope); element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); $animate.triggerReflow(); var now = Date.now(); browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 }); element.css('width', '200px'); browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2 }); expect(element.css('width')).toBe("200px"); })); it("should animate for the highest duration", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-transition:1s linear all 2s;' + 'transition:1s linear all 2s;' + '-webkit-animation:my_ani 10s 1s;' + 'animation:my_ani 10s 1s;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      foo
                      '))($rootScope); element.addClass('ng-hide'); expect(element).toBeHidden(); $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { $animate.triggerReflow(); } expect(element).toBeShown(); if ($sniffer.transitions) { expect(element.hasClass('ng-hide-remove-active')).toBe(true); browserTrigger(element,'animationend', { timeStamp: Date.now() + 11000, elapsedTime: 11 }); expect(element.hasClass('ng-hide-remove-active')).toBe(false); } })); it("should finish the previous transition when a new animation is started", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { var style = '-webkit-transition: 1s linear all;' + 'transition: 1s linear all;'; ss.addRule('.ng-hide-add', style); ss.addRule('.ng-hide-remove', style); element = $compile(html('
                      1
                      '))($rootScope); element.addClass('ng-hide'); $animate.removeClass(element, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('ng-hide-remove')).toBe(true); expect(element.hasClass('ng-hide-remove-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(element.hasClass('ng-hide-remove')).toBe(false); expect(element.hasClass('ng-hide-remove-active')).toBe(false); expect(element).toBeShown(); $animate.addClass(element, 'ng-hide'); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('ng-hide-add')).toBe(true); expect(element.hasClass('ng-hide-add-active')).toBe(true); } })); it("should stagger the items when the correct CSS class is provided", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { if(!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave, .real-animation-fake.ng-enter, .real-animation-fake.ng-leave', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.real-animation.ng-enter-stagger, .real-animation.ng-leave-stagger', '-webkit-transition-delay:0.1s;' + '-webkit-transition-duration:0s;' + 'transition-delay:0.1s;' + 'transition-duration:0s;'); ss.addRule('.fake-animation.ng-enter-stagger, .fake-animation.ng-leave-stagger', '-webkit-transition-delay:0.1s;' + '-webkit-transition-duration:1s;' + 'transition-delay:0.1s;' + 'transition-duration:1s;'); var container = $compile(html('
                      '))($rootScope); var elements = []; for(var i = 0; i < 5; i++) { var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements.push(element); }; $rootScope.$digest(); $animate.triggerReflow(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).toMatch(/transition-delay: 0\.1\d*s/); expect(elements[2].attr('style')).toMatch(/transition-delay: 0\.2\d*s/); expect(elements[3].attr('style')).toMatch(/transition-delay: 0\.3\d*s/); expect(elements[4].attr('style')).toMatch(/transition-delay: 0\.4\d*s/); for(var i = 0; i < 5; i++) { dealoc(elements[i]); var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements[i] = element; }; $rootScope.$digest(); var expectFailure = true; try { $animate.triggerReflow(); expectFailure = false; } catch(e) {} expect(expectFailure).toBe(true); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).not.toMatch(/transition-delay: 0\.1\d*s/); expect(elements[2].attr('style')).not.toMatch(/transition-delay: 0\.2\d*s/); expect(elements[3].attr('style')).not.toMatch(/transition-delay: 0\.3\d*s/); expect(elements[4].attr('style')).not.toMatch(/transition-delay: 0\.4\d*s/); })); it("should stagger items when multiple transition durations/delays are defined", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { if(!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.stagger-animation.ng-enter, .ani.ng-leave', '-webkit-transition:1s linear color 2s, 3s linear font-size 4s;' + 'transition:1s linear color 2s, 3s linear font-size 4s;'); ss.addRule('.stagger-animation.ng-enter-stagger, .ani.ng-leave-stagger', '-webkit-transition-delay:0.1s;' + 'transition-delay:0.1s;'); var container = $compile(html('
                      '))($rootScope); var elements = []; for(var i = 0; i < 4; i++) { var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements.push(element); }; $rootScope.$digest(); $animate.triggerReflow(); expect(elements[0].attr('style')).toMatch(/transition-duration: 1\d*s,\s*3\d*s;/); expect(elements[0].attr('style')).not.toContain('transition-delay'); expect(elements[1].attr('style')).toMatch(/transition-delay: 2\.1\d*s,\s*4\.1\d*s/); expect(elements[2].attr('style')).toMatch(/transition-delay: 2\.2\d*s,\s*4\.2\d*s/); expect(elements[3].attr('style')).toMatch(/transition-delay: 2\.3\d*s,\s*4\.3\d*s/); })); it("should apply a closing timeout to close all pending transitions", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.animated-element', '-webkit-transition:5s linear all;' + 'transition:5s linear all;'); element = $compile(html('
                      foo
                      '))($rootScope); $animate.addClass(element, 'some-class'); $animate.triggerReflow(); //reflow expect(element.hasClass('some-class-add-active')).toBe(true); $timeout.flush(7500); //closing timeout expect(element.hasClass('some-class-add-active')).toBe(false); })); it("apply a closing timeout with respect to a staggering animation", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.entering-element.ng-enter', '-webkit-transition:5s linear all;' + 'transition:5s linear all;'); ss.addRule('.entering-element.ng-enter-stagger', '-webkit-transition-delay:0.5s;' + 'transition-delay:0.5s;'); element = $compile(html('
                      '))($rootScope); var kids = []; for(var i = 0; i < 5; i++) { kids.push(angular.element('
                      ')); $animate.enter(kids[i], element); } $rootScope.$digest(); $animate.triggerReflow(); //reflow expect(element.children().length).toBe(5); for(var i = 0; i < 5; i++) { expect(kids[i].hasClass('ng-enter-active')).toBe(true); } $timeout.flush(7500); for(var i = 0; i < 5; i++) { expect(kids[i].hasClass('ng-enter-active')).toBe(true); } //(stagger * index) + (duration + delay) * 150% $timeout.flush(9500); //0.5 * 4 + 5 * 1.5 = 9500; for(var i = 0; i < 5; i++) { expect(kids[i].hasClass('ng-enter-active')).toBe(false); } })); it("should not allow the closing animation to close off a successive animation midway", inject(function($animate, $rootScope, $compile, $sniffer, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.some-class-add', '-webkit-transition:5s linear all;' + 'transition:5s linear all;'); ss.addRule('.some-class-remove', '-webkit-transition:10s linear all;' + 'transition:10s linear all;'); element = $compile(html('
                      foo
                      '))($rootScope); $animate.addClass(element, 'some-class'); $animate.triggerReflow(); //reflow expect(element.hasClass('some-class-add-active')).toBe(true); $animate.removeClass(element, 'some-class'); $animate.triggerReflow(); //second reflow $timeout.flush(7500); //closing timeout for the first animation expect(element.hasClass('some-class-remove-active')).toBe(true); $timeout.flush(15000); //closing timeout for the second animation expect(element.hasClass('some-class-remove-active')).toBe(false); $timeout.verifyNoPendingTasks(); })); }); it("should apply staggering to both transitions and keyframe animations when used within the same animation", inject(function($animate, $rootScope, $compile, $sniffer, $timeout, $document, $rootElement) { if(!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.stagger-animation.ng-enter, .stagger-animation.ng-leave', '-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' + 'animation:my_animation 1s 1s, your_animation 1s 2s;' + '-webkit-transition:1s linear all 1s;' + 'transition:1s linear all 1s;'); ss.addRule('.stagger-animation.ng-enter-stagger, .stagger-animation.ng-leave-stagger', '-webkit-transition-delay:0.1s;' + 'transition-delay:0.1s;' + '-webkit-animation-delay:0.2s;' + 'animation-delay:0.2s;'); var container = $compile(html('
                      '))($rootScope); var elements = []; for(var i = 0; i < 3; i++) { var newScope = $rootScope.$new(); var element = $compile('
                      ')(newScope); $animate.enter(element, container); elements.push(element); }; $rootScope.$digest(); $animate.triggerReflow(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).toMatch(/transition-delay:\s+1.1\d*/); expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.2\d*s,\s*2\.2\d*s/); expect(elements[2].attr('style')).toMatch(/transition-delay:\s+1.2\d*/); expect(elements[2].attr('style')).toMatch(/animation-delay: 1\.4\d*s,\s*2\.4\d*s/); for(var i = 0; i < 3; i++) { browserTrigger(elements[i],'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 }); expect(elements[i].attr('style')).toBeFalsy(); } })); }); describe('animation evaluation', function () { it('should re-evaluate the CSS classes for an animation each time', inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $compile) { ss.addRule('.abc.ng-enter', '-webkit-transition:22s linear all;' + 'transition:22s linear all;'); ss.addRule('.xyz.ng-enter', '-webkit-transition:11s linear all;' + 'transition:11s linear all;'); var parent = $compile('
                      ')($rootScope); var element = parent.find('span'); $rootElement.append(parent); angular.element(document.body).append($rootElement); $rootScope.klass = 'abc'; $animate.enter(element, parent); $rootScope.$digest(); if ($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('abc')).toBe(true); expect(element.hasClass('ng-enter')).toBe(true); expect(element.hasClass('ng-enter-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22 }); $timeout.flush(); } expect(element.hasClass('abc')).toBe(true); $rootScope.klass = 'xyz'; $animate.enter(element, parent); $rootScope.$digest(); if ($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('xyz')).toBe(true); expect(element.hasClass('ng-enter')).toBe(true); expect(element.hasClass('ng-enter-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 }); $timeout.flush(); } expect(element.hasClass('xyz')).toBe(true); })); it('should only append active to the newly append CSS className values', inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { ss.addRule('.ng-enter', '-webkit-transition:9s linear all;' + 'transition:9s linear all;'); var parent = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); angular.element(document.body).append($rootElement); element.attr('class','one two'); $animate.enter(element, parent); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('one')).toBe(true); expect(element.hasClass('two')).toBe(true); expect(element.hasClass('ng-enter')).toBe(true); expect(element.hasClass('ng-enter-active')).toBe(true); expect(element.hasClass('one-active')).toBe(false); expect(element.hasClass('two-active')).toBe(false); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 }); } expect(element.hasClass('one')).toBe(true); expect(element.hasClass('two')).toBe(true); })); }); describe("Callbacks", function() { beforeEach(function() { module(function($animateProvider) { $animateProvider.register('.custom', function($timeout) { return { removeClass : function(element, className, done) { $timeout(done, 2000); } } }); $animateProvider.register('.other', function() { return { enter : function(element, done) { $timeout(done, 10000); } } }); }) }); it("should fire the enter callback", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); body.append($rootElement); var flag = false; $animate.enter(element, parent, null, function() { flag = true; }); $rootScope.$digest(); $timeout.flush(); expect(flag).toBe(true); })); it("should fire the leave callback", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); body.append($rootElement); var flag = false; $animate.leave(element, function() { flag = true; }); $rootScope.$digest(); $timeout.flush(); expect(flag).toBe(true); })); it("should fire the move callback", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); var parent2 = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); body.append($rootElement); var flag = false; $animate.move(element, parent, parent2, function() { flag = true; }); $rootScope.$digest(); $timeout.flush(); expect(flag).toBe(true); expect(element.parent().id).toBe(parent2.id); })); it("should fire the addClass/removeClass callbacks", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); body.append($rootElement); var signature = ''; $animate.addClass(element, 'on', function() { signature += 'A'; }); $animate.removeClass(element, 'on', function() { signature += 'B'; }); $timeout.flush(); expect(signature).toBe('AB'); })); it('should fire DOM callbacks on the element being animated', inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { if(!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.klass-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); var element = jqLite('
                      '); $rootElement.append(element); body.append($rootElement); var steps = []; element.on('$animate:before', function(e, data) { steps.push(['before', data.className, data.event]); }); element.on('$animate:after', function(e, data) { steps.push(['after', data.className, data.event]); }); element.on('$animate:close', function(e, data) { steps.push(['close', data.className, data.event]); }); $animate.addClass(element, 'klass', function() { steps.push(['done', 'klass', 'addClass']); }); $timeout.flush(1); expect(steps.pop()).toEqual(['before', 'klass', 'addClass']); $animate.triggerReflow(); $timeout.flush(1); expect(steps.pop()).toEqual(['after', 'klass', 'addClass']); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); $timeout.flush(1); expect(steps.shift()).toEqual(['close', 'klass', 'addClass']); expect(steps.shift()).toEqual(['done', 'klass', 'addClass']); })); it('should fire the DOM callbacks even if no animation is rendered', inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { $animate.enabled(true); var parent = jqLite('
                      '); var element = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var steps = []; element.on('$animate:before', function(e, data) { steps.push(['before', data.className, data.event]); }); element.on('$animate:after', function(e, data) { steps.push(['after', data.className, data.event]); }); $animate.enter(element, parent); $rootScope.$digest(); $timeout.flush(1); expect(steps.shift()).toEqual(['before', 'ng-enter', 'enter']); expect(steps.shift()).toEqual(['after', 'ng-enter', 'enter']); })); it("should fire a done callback when provided with no animation", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); var element = parent.find('span'); $rootElement.append(parent); body.append($rootElement); var flag = false; $animate.removeClass(element, 'ng-hide', function() { flag = true; }); $timeout.flush(); expect(flag).toBe(true); })); it("should fire a done callback when provided with a css animation/transition", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { ss.addRule('.ng-hide-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.ng-hide-remove', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = parent.find('span'); var flag = false; $animate.removeClass(element, 'ng-hide', function() { flag = true; }); if($sniffer.transitions) { browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } $timeout.flush(); expect(flag).toBe(true); })); it("should fire a done callback when provided with a JS animation", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = parent.find('span'); element.addClass('custom'); var flag = false; $animate.removeClass(element, 'ng-hide', function() { flag = true; }); $timeout.flush(); expect(flag).toBe(true); })); it("should fire the callback right away if another animation is called right after", inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) { ss.addRule('.ng-hide-add', '-webkit-transition:9s linear all;' + 'transition:9s linear all;'); ss.addRule('.ng-hide-remove', '-webkit-transition:9s linear all;' + 'transition:9s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = parent.find('span'); var signature = ''; $animate.removeClass(element, 'ng-hide', function() { signature += 'A'; }); $animate.addClass(element, 'ng-hide', function() { signature += 'B'; }); $animate.addClass(element, 'ng-hide'); //earlier animation cancelled if($sniffer.transitions) { $animate.triggerReflow(); } $timeout.flush(); expect(signature).toBe('AB'); })); }); describe("addClass / removeClass", function() { var captured; beforeEach(function() { module(function($animateProvider, $provide) { $animateProvider.register('.klassy', function($timeout) { return { addClass : function(element, className, done) { captured = 'addClass-' + className; $timeout(done, 500, false); }, removeClass : function(element, className, done) { captured = 'removeClass-' + className; $timeout(done, 3000, false); } } }); }); }); it("should not perform an animation, and the followup DOM operation, if the class is " + "already present during addClass or not present during removeClass on the element", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { var element = jqLite('
                      '); $rootElement.append(element); body.append($rootElement); //skipped animations captured = 'none'; $animate.removeClass(element, 'some-class'); expect(element.hasClass('some-class')).toBe(false); expect(captured).toBe('none'); element.addClass('some-class'); captured = 'nothing'; $animate.addClass(element, 'some-class'); expect(captured).toBe('nothing'); expect(element.hasClass('some-class')).toBe(true); //actual animations captured = 'none'; $animate.removeClass(element, 'some-class'); $timeout.flush(); expect(element.hasClass('some-class')).toBe(false); expect(captured).toBe('removeClass-some-class'); captured = 'nothing'; $animate.addClass(element, 'some-class'); $timeout.flush(); expect(element.hasClass('some-class')).toBe(true); expect(captured).toBe('addClass-some-class'); })); it("should add and remove CSS classes after an animation even if no animation is present", inject(function($animate, $rootScope, $sniffer, $rootElement) { var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); $animate.addClass(element,'klass'); expect(element.hasClass('klass')).toBe(true); $animate.removeClass(element,'klass'); expect(element.hasClass('klass')).toBe(false); expect(element.hasClass('klass-remove')).toBe(false); expect(element.hasClass('klass-remove-active')).toBe(false); })); it("should add and remove CSS classes with a callback", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); var signature = ''; $animate.addClass(element,'klass', function() { signature += 'A'; }); expect(element.hasClass('klass')).toBe(true); $animate.removeClass(element,'klass', function() { signature += 'B'; }); $timeout.flush(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('AB'); })); it("should end the current addClass animation, add the CSS class and then run the removeClass animation", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { ss.addRule('.klass-add', '-webkit-transition:3s linear all;' + 'transition:3s linear all;'); ss.addRule('.klass-remove', '-webkit-transition:3s linear all;' + 'transition:3s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); var signature = ''; $animate.addClass(element,'klass', function() { signature += '1'; }); if($sniffer.transitions) { expect(element.hasClass('klass-add')).toBe(true); $animate.triggerReflow(); expect(element.hasClass('klass')).toBe(true); expect(element.hasClass('klass-add-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 }); } $timeout.flush(); //this cancels out the older animation $animate.removeClass(element,'klass', function() { signature += '2'; }); if($sniffer.transitions) { expect(element.hasClass('klass-remove')).toBe(true); $animate.triggerReflow(); expect(element.hasClass('klass')).toBe(false); expect(element.hasClass('klass-add')).toBe(false); expect(element.hasClass('klass-add-active')).toBe(false); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 }); } $timeout.flush(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('12'); })); it("should properly execute JS animations and use callbacks when using addClass / removeClass", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); var signature = ''; $animate.addClass(element,'klassy', function() { signature += 'X'; }); $timeout.flush(500); expect(element.hasClass('klassy')).toBe(true); $animate.removeClass(element,'klassy', function() { signature += 'Y'; }); $timeout.flush(3000); expect(element.hasClass('klassy')).toBe(false); expect(signature).toBe('XY'); })); it("should properly execute CSS animations/transitions and use callbacks when using addClass / removeClass", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { ss.addRule('.klass-add', '-webkit-transition:11s linear all;' + 'transition:11s linear all;'); ss.addRule('.klass-remove', '-webkit-transition:11s linear all;' + 'transition:11s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); var signature = ''; $animate.addClass(element,'klass', function() { signature += 'd'; }); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('klass-add')).toBe(true); expect(element.hasClass('klass-add-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 }); expect(element.hasClass('klass-add')).toBe(false); expect(element.hasClass('klass-add-active')).toBe(false); } $timeout.flush(); expect(element.hasClass('klass')).toBe(true); $animate.removeClass(element,'klass', function() { signature += 'b'; }); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('klass-remove')).toBe(true); expect(element.hasClass('klass-remove-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 }); expect(element.hasClass('klass-remove')).toBe(false); expect(element.hasClass('klass-remove-active')).toBe(false); } $timeout.flush(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('db'); })); it("should allow for multiple css classes to be animated plus a callback when added", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { ss.addRule('.one-add', '-webkit-transition:7s linear all;' + 'transition:7s linear all;'); ss.addRule('.two-add', '-webkit-transition:7s linear all;' + 'transition:7s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); var flag = false; $animate.addClass(element,'one two', function() { flag = true; }); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('one-add')).toBe(true); expect(element.hasClass('two-add')).toBe(true); expect(element.hasClass('one-add-active')).toBe(true); expect(element.hasClass('two-add-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 7000, elapsedTime: 7 }); expect(element.hasClass('one-add')).toBe(false); expect(element.hasClass('one-add-active')).toBe(false); expect(element.hasClass('two-add')).toBe(false); expect(element.hasClass('two-add-active')).toBe(false); } $timeout.flush(); expect(element.hasClass('one')).toBe(true); expect(element.hasClass('two')).toBe(true); expect(flag).toBe(true); })); it("should allow for multiple css classes to be animated plus a callback when removed", inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { ss.addRule('.one-remove', '-webkit-transition:9s linear all;' + 'transition:9s linear all;'); ss.addRule('.two-remove', '-webkit-transition:9s linear all;' + 'transition:9s linear all;'); var parent = jqLite('
                      '); $rootElement.append(parent); body.append($rootElement); var element = jqLite(parent.find('span')); element.addClass('one two'); expect(element.hasClass('one')).toBe(true); expect(element.hasClass('two')).toBe(true); var flag = false; $animate.removeClass(element,'one two', function() { flag = true; }); if($sniffer.transitions) { $animate.triggerReflow(); expect(element.hasClass('one-remove')).toBe(true); expect(element.hasClass('two-remove')).toBe(true); expect(element.hasClass('one-remove-active')).toBe(true); expect(element.hasClass('two-remove-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 9000, elapsedTime: 9 }); expect(element.hasClass('one-remove')).toBe(false); expect(element.hasClass('one-remove-active')).toBe(false); expect(element.hasClass('two-remove')).toBe(false); expect(element.hasClass('two-remove-active')).toBe(false); } $timeout.flush(); expect(element.hasClass('one')).toBe(false); expect(element.hasClass('two')).toBe(false); expect(flag).toBe(true); })); }); }); var $rootElement, $document; beforeEach(module(function() { return function(_$rootElement_, _$document_, $animate) { $rootElement = _$rootElement_; $document = _$document_; $animate.enabled(true); } })); function html(element) { var body = jqLite($document[0].body); $rootElement.append(element); body.append($rootElement); return element; } it("should properly animate and parse CSS3 transitions", inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { ss.addRule('.ng-enter', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); $animate.enter(child, element); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); })); it("should properly animate and parse CSS3 animations", inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { ss.addRule('.ng-enter', '-webkit-animation: some_animation 4s linear 1s 2 alternate;' + 'animation: some_animation 4s linear 1s 2 alternate;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); $animate.enter(child, element); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 9000, elapsedTime: 9 }); } expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); })); it("should skip animations if the browser does not support CSS3 transitions and CSS3 animations", inject(function($compile, $rootScope, $animate, $sniffer) { $sniffer.animations = false; $sniffer.transitions = false; ss.addRule('.ng-enter', '-webkit-animation: some_animation 4s linear 1s 2 alternate;' + 'animation: some_animation 4s linear 1s 2 alternate;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); expect(child.hasClass('ng-enter')).toBe(false); $animate.enter(child, element); $rootScope.$digest(); expect(child.hasClass('ng-enter')).toBe(false); })); it("should run other defined animations inline with CSS3 animations", function() { module(function($animateProvider) { $animateProvider.register('.custom', function($timeout) { return { enter : function(element, done) { element.addClass('i-was-animated'); $timeout(done, 10, false); } } }); }) inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { ss.addRule('.ng-enter', '-webkit-transition: 1s linear all;' + 'transition: 1s linear all;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); expect(child.hasClass('i-was-animated')).toBe(false); child.addClass('custom'); $animate.enter(child, element); $rootScope.$digest(); if($sniffer.transitions) { $animate.triggerReflow(); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } expect(child.hasClass('i-was-animated')).toBe(true); }); }); it("should properly cancel CSS transitions or animations if another animation is fired", function() { module(function($animateProvider) { $animateProvider.register('.usurper', function($timeout) { return { leave : function(element, done) { element.addClass('this-is-mine-now'); $timeout(done, 55, false); } } }); }); inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { ss.addRule('.ng-enter', '-webkit-transition: 2s linear all;' + 'transition: 2s linear all;'); ss.addRule('.ng-leave', '-webkit-transition: 2s linear all;' + 'transition: 2s linear all;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); $animate.enter(child, element); $rootScope.$digest(); //this is added/removed right away otherwise if($sniffer.transitions) { $animate.triggerReflow(); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); } expect(child.hasClass('this-is-mine-now')).toBe(false); child.addClass('usurper'); $animate.leave(child); $rootScope.$digest(); $timeout.flush(); expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); expect(child.hasClass('usurper')).toBe(true); expect(child.hasClass('this-is-mine-now')).toBe(true); $timeout.flush(55); }); }); it("should not perform the active class animation if the animation has been cancelled before the reflow occurs", function() { inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { if(!$sniffer.transitions) return; ss.addRule('.animated.ng-enter', '-webkit-transition: 2s linear all;' + 'transition: 2s linear all;'); var element = html($compile('
                      ...
                      ')($rootScope)); var child = $compile('
                      ...
                      ')($rootScope); $animate.enter(child, element); $rootScope.$digest(); expect(child.hasClass('ng-enter')).toBe(true); $animate.leave(child); $rootScope.$digest(); $animate.triggerReflow(); expect(child.hasClass('ng-enter-active')).toBe(false); }); }); // // it("should add and remove CSS classes and perform CSS animations during the process", // inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { // // ss.addRule('.on-add', '-webkit-transition: 10s linear all; ' + // 'transition: 10s linear all;'); // ss.addRule('.on-remove', '-webkit-transition: 10s linear all; ' + // 'transition: 10s linear all;'); // // var element = html($compile('
                      ')($rootScope)); // // expect(element.hasClass('on')).toBe(false); // // $animate.addClass(element, 'on'); // // if($sniffer.transitions) { // expect(element.hasClass('on')).toBe(false); // expect(element.hasClass('on-add')).toBe(true); // $timeout.flush(); // } // // $timeout.flush(); // // expect(element.hasClass('on')).toBe(true); // expect(element.hasClass('on-add')).toBe(false); // expect(element.hasClass('on-add-active')).toBe(false); // // $animate.removeClass(element, 'on'); // if($sniffer.transitions) { // expect(element.hasClass('on')).toBe(true); // expect(element.hasClass('on-remove')).toBe(true); // $timeout.flush(10000); // } // // $timeout.flush(); // expect(element.hasClass('on')).toBe(false); // expect(element.hasClass('on-remove')).toBe(false); // expect(element.hasClass('on-remove-active')).toBe(false); // })); // // // it("should show and hide elements with CSS & JS animations being performed in the process", function() { // module(function($animateProvider) { // $animateProvider.register('.displayer', function($timeout) { // return { // removeClass : function(element, className, done) { // element.removeClass('hiding'); // element.addClass('showing'); // $timeout(done, 25, false); // }, // addClass : function(element, className, done) { // element.removeClass('showing'); // element.addClass('hiding'); // $timeout(done, 555, false); // } // } // }); // }) // inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { // // ss.addRule('.ng-hide-add', '-webkit-transition: 5s linear all;' + // 'transition: 5s linear all;'); // ss.addRule('.ng-hide-remove', '-webkit-transition: 5s linear all;' + // 'transition: 5s linear all;'); // // var element = html($compile('
                      ')($rootScope)); // // element.addClass('displayer'); // // expect(element).toBeShown(); // expect(element.hasClass('showing')).toBe(false); // expect(element.hasClass('hiding')).toBe(false); // // $animate.addClass(element, 'ng-hide'); // // if($sniffer.transitions) { // expect(element).toBeShown(); //still showing // $timeout.flush(); // expect(element).toBeShown(); // $timeout.flush(5555); // } // $timeout.flush(); // expect(element).toBeHidden(); // // expect(element.hasClass('showing')).toBe(false); // expect(element.hasClass('hiding')).toBe(true); // $animate.removeClass(element, 'ng-hide'); // // if($sniffer.transitions) { // expect(element).toBeHidden(); // $timeout.flush(); // expect(element).toBeHidden(); // $timeout.flush(5580); // } // $timeout.flush(); // expect(element).toBeShown(); // // expect(element.hasClass('showing')).toBe(true); // expect(element.hasClass('hiding')).toBe(false); // }); // }); it("should remove all the previous classes when the next animation is applied before a reflow", function() { var fn, interceptedClass; module(function($animateProvider) { $animateProvider.register('.three', function() { return { move : function(element, done) { fn = function() { done(); } return function() { interceptedClass = element.attr('class'); } } } }); }); inject(function($compile, $rootScope, $animate, $timeout) { var parent = html($compile('
                      ')($rootScope)); var one = $compile('
                      ')($rootScope); var two = $compile('
                      ')($rootScope); var three = $compile('
                      ')($rootScope); parent.append(one); parent.append(two); parent.append(three); $animate.move(three, null, two); $rootScope.$digest(); $animate.move(three, null, one); $rootScope.$digest(); //this means that the former animation was cleaned up before the new one starts expect(interceptedClass.indexOf('ng-animate') >= 0).toBe(false); }); }); it("should provide the correct CSS class to the addClass and removeClass callbacks within a JS animation", function() { module(function($animateProvider) { $animateProvider.register('.classify', function() { return { removeClass : function(element, className, done) { element.data('classify','remove-' + className); done(); }, addClass : function(element, className, done) { element.data('classify','add-' + className); done(); } } }); }) inject(function($compile, $rootScope, $animate) { var element = html($compile('
                      ')($rootScope)); $animate.addClass(element, 'super'); expect(element.data('classify')).toBe('add-super'); $animate.removeClass(element, 'super'); expect(element.data('classify')).toBe('remove-super'); $animate.addClass(element, 'superguy'); expect(element.data('classify')).toBe('add-superguy'); }); }); it("should not skip ngAnimate animations when any pre-existing CSS transitions are present on the element", function() { inject(function($compile, $rootScope, $animate, $timeout, $sniffer) { if(!$sniffer.transitions) return; var element = html($compile('
                      ')($rootScope)); var child = html($compile('
                      ')($rootScope)); ss.addRule('.animated', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.super-add', '-webkit-transition:2s linear all;' + 'transition:2s linear all;'); $rootElement.append(element); jqLite(document.body).append($rootElement); $animate.addClass(element, 'super'); var empty = true; try { $animate.triggerReflow(); empty = false; } catch(e) {} expect(empty).toBe(false); }); }); it("should wait until both the duration and delay are complete to close off the animation", inject(function($compile, $rootScope, $animate, $timeout, $sniffer) { if(!$sniffer.transitions) return; var element = html($compile('
                      ')($rootScope)); var child = html($compile('
                      ')($rootScope)); ss.addRule('.animated.ng-enter', '-webkit-transition: width 1s, background 1s 1s;' + 'transition: width 1s, background 1s 1s;'); $rootElement.append(element); jqLite(document.body).append($rootElement); $animate.enter(child, element); $rootScope.$digest(); $animate.triggerReflow(); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0 }); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 2000, elapsedTime: 2 }); expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); expect(element.contents().length).toBe(1); })); it("should cancel all child animations when a leave or move animation is triggered on a parent element", function() { var step, animationState; module(function($animateProvider) { $animateProvider.register('.animan', function($timeout) { return { enter : function(element, done) { animationState = 'enter'; step = done; return function(cancelled) { animationState = cancelled ? 'enter-cancel' : animationState; } }, addClass : function(element, className, done) { animationState = 'addClass'; step = done; return function(cancelled) { animationState = cancelled ? 'addClass-cancel' : animationState; } } }; }); }); inject(function($animate, $compile, $rootScope, $timeout, $sniffer) { var element = html($compile('
                      ')($rootScope)); var container = html($compile('
                      ')($rootScope)); var child = html($compile('
                      ')($rootScope)); ss.addRule('.animan.ng-enter, .animan.something-add', '-webkit-transition: width 1s, background 1s 1s;' + 'transition: width 1s, background 1s 1s;'); $rootElement.append(element); jqLite(document.body).append($rootElement); $animate.enter(child, element); $rootScope.$digest(); expect(animationState).toBe('enter'); if($sniffer.transitions) { expect(child.hasClass('ng-enter')).toBe(true); $animate.triggerReflow(); expect(child.hasClass('ng-enter-active')).toBe(true); } $animate.move(element, container); if($sniffer.transitions) { expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); } expect(animationState).toBe('enter-cancel'); $rootScope.$digest(); $timeout.flush(); $animate.addClass(child, 'something'); if($sniffer.transitions) { $animate.triggerReflow(); } expect(animationState).toBe('addClass'); if($sniffer.transitions) { expect(child.hasClass('something-add')).toBe(true); expect(child.hasClass('something-add-active')).toBe(true); } $animate.leave(container); expect(animationState).toBe('addClass-cancel'); if($sniffer.transitions) { expect(child.hasClass('something-add')).toBe(false); expect(child.hasClass('something-add-active')).toBe(false); } }); }); it("should wait until a queue of animations are complete before performing a reflow", inject(function($rootScope, $compile, $timeout, $sniffer, $animate) { if(!$sniffer.transitions) return; $rootScope.items = [1,2,3,4,5]; var element = html($compile('
                      ')($rootScope)); ss.addRule('.animated.ng-enter', '-webkit-transition: width 1s, background 1s 1s;' + 'transition: width 1s, background 1s 1s;'); $rootScope.$digest(); expect(element[0].querySelectorAll('.ng-enter-active').length).toBe(0); $animate.triggerReflow(); expect(element[0].querySelectorAll('.ng-enter-active').length).toBe(5); forEach(element.children(), function(kid) { browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); }); expect(element[0].querySelectorAll('.ng-enter-active').length).toBe(0); })); it("should work to disable all child animations for an element", function() { var childAnimated = false, containerAnimated = false; module(function($animateProvider) { $animateProvider.register('.child', function() { return { addClass : function(element, className, done) { childAnimated = true; done(); } } }); $animateProvider.register('.container', function() { return { leave : function(element, done) { containerAnimated = true; done(); } } }); }); inject(function($compile, $rootScope, $animate, $timeout, $rootElement) { $animate.enabled(true); var element = $compile('
                      ')($rootScope); jqLite($document[0].body).append($rootElement); $rootElement.append(element); var child = $compile('
                      ')($rootScope); element.append(child); $animate.enabled(true, element); $animate.addClass(child, 'awesome'); expect(childAnimated).toBe(true); childAnimated = false; $animate.enabled(false, element); $animate.addClass(child, 'super'); expect(childAnimated).toBe(false); $animate.leave(element); $rootScope.$digest(); expect(containerAnimated).toBe(true); }); }); it("should disable all child animations on structural animations until the post animation timeout has passed", function() { var intercepted; module(function($animateProvider) { $animateProvider.register('.animated', function() { return { enter : ani('enter'), leave : ani('leave'), move : ani('move'), addClass : ani('addClass'), removeClass : ani('removeClass') }; function ani(type) { return function(element, className, done) { intercepted = type; (done || className)(); } } }); }); inject(function($animate, $rootScope, $sniffer, $timeout, $compile, _$rootElement_) { $rootElement = _$rootElement_; $animate.enabled(true); $rootScope.$digest(); var element = $compile('
                      ...
                      ')($rootScope); var child1 = $compile('
                      ...
                      ')($rootScope); var child2 = $compile('
                      ...
                      ')($rootScope); var container = $compile('
                      ...
                      ')($rootScope); jqLite($document[0].body).append($rootElement); $rootElement.append(container); element.append(child1); element.append(child2); $animate.move(element, null, container); $rootScope.$digest(); expect(intercepted).toBe('move'); $animate.addClass(child1, 'test'); expect(child1.hasClass('test')).toBe(true); expect(intercepted).toBe('move'); $animate.leave(child1); $rootScope.$digest(); expect(intercepted).toBe('move'); //reflow has passed $timeout.flush(); $animate.leave(child2); $rootScope.$digest(); expect(intercepted).toBe('leave'); }); }); it("should not disable any child animations when any parent class-based animations are run", function() { var intercepted; module(function($animateProvider) { $animateProvider.register('.animated', function() { return { enter : function(element, done) { intercepted = true; done(); } } }); }); inject(function($animate, $rootScope, $sniffer, $timeout, $compile, $document, $rootElement) { $animate.enabled(true); var element = $compile('
                      value
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $rootScope.bool = true; $rootScope.$digest(); expect(intercepted).toBe(true); }); }); it("should cache the response from getComputedStyle if each successive element has the same className value and parent until the first reflow hits", function() { var count = 0; module(function($provide) { $provide.value('$window', { document : jqLite(window.document), getComputedStyle: function(element) { count++; return window.getComputedStyle(element); } }); }); inject(function($animate, $rootScope, $compile, $rootElement, $timeout, $document, $sniffer) { if(!$sniffer.transitions) return; $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); for(var i=0;i<20;i++) { var kid = $compile('
                      ')($rootScope); $animate.enter(kid, element); } $rootScope.$digest(); //called three times since the classname is the same expect(count).toBe(2); dealoc(element); count = 0; for(var i=0;i<20;i++) { var kid = $compile('
                      ')($rootScope); $animate.enter(kid, element); } $rootScope.$digest(); expect(count).toBe(20); }); }); it("should cancel an ongoing class-based animation only if the new class contains transition/animation CSS code", inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.green-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.blue-add', 'background:blue;'); ss.addRule('.red-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.yellow-add', '-webkit-animation: some_animation 4s linear 1s 2 alternate;' + 'animation: some_animation 4s linear 1s 2 alternate;'); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.addClass(element, 'green'); expect(element.hasClass('green-add')).toBe(true); $animate.addClass(element, 'blue'); expect(element.hasClass('blue')).toBe(true); expect(element.hasClass('green-add')).toBe(true); //not cancelled $animate.addClass(element, 'red'); expect(element.hasClass('green-add')).toBe(false); expect(element.hasClass('red-add')).toBe(true); $animate.addClass(element, 'yellow'); expect(element.hasClass('red-add')).toBe(false); expect(element.hasClass('yellow-add')).toBe(true); })); it("should cancel and perform the dom operation only after the reflow has run", inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.green-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.red-add', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.addClass(element, 'green'); expect(element.hasClass('green-add')).toBe(true); $animate.addClass(element, 'red'); expect(element.hasClass('red-add')).toBe(true); expect(element.hasClass('green')).toBe(false); expect(element.hasClass('red')).toBe(false); $animate.triggerReflow(); expect(element.hasClass('green')).toBe(true); expect(element.hasClass('red')).toBe(true); })); it("should avoid mixing up substring classes during add and remove operations", function() { var currentAnimation, currentFn; module(function($animateProvider) { $animateProvider.register('.on', function() { return { beforeAddClass : function(element, className, done) { currentAnimation = 'addClass'; currentFn = done; return function(cancelled) { currentAnimation = cancelled ? null : currentAnimation; } }, beforeRemoveClass : function(element, className, done) { currentAnimation = 'removeClass'; currentFn = done; return function(cancelled) { currentAnimation = cancelled ? null : currentAnimation; } } }; }); }); inject(function($compile, $rootScope, $animate, $sniffer, $timeout) { var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.addClass(element, 'on'); expect(currentAnimation).toBe('addClass'); currentFn(); currentAnimation = null; $animate.removeClass(element, 'on'); $animate.addClass(element, 'on'); expect(currentAnimation).toBe(null); }); }); it('should enable and disable animations properly on the root element', function() { var count = 0; module(function($animateProvider) { $animateProvider.register('.animated', function() { return { addClass : function(element, className, done) { count++; done(); } } }); }); inject(function($compile, $rootScope, $animate, $sniffer, $rootElement, $timeout) { $rootElement.addClass('animated'); $animate.addClass($rootElement, 'green'); expect(count).toBe(1); $animate.addClass($rootElement, 'red'); expect(count).toBe(2); }); }); it('should perform pre and post animations', function() { var steps = []; module(function($animateProvider) { $animateProvider.register('.class-animate', function() { return { beforeAddClass : function(element, className, done) { steps.push('before'); done(); }, addClass : function(element, className, done) { steps.push('after'); done(); } }; }); }); inject(function($animate, $rootScope, $compile, $rootElement, $timeout) { $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); $animate.addClass(element, 'red'); expect(steps).toEqual(['before','after']); }); }); it('should treat the leave event always as a before event and discard the beforeLeave function', function() { var parentID, steps = []; module(function($animateProvider) { $animateProvider.register('.animate', function() { return { beforeLeave : function(element, done) { steps.push('before'); done(); }, leave : function(element, done) { parentID = element.parent().attr('id'); steps.push('after'); done(); } }; }); }); inject(function($animate, $rootScope, $compile, $rootElement) { $animate.enabled(true); var element = $compile('
                      ')($rootScope); var child = $compile('
                      ')($rootScope); $rootElement.append(element); element.append(child); $animate.leave(child); $rootScope.$digest(); expect(steps).toEqual(['after']); expect(parentID).toEqual('parentGuy'); }); }); it('should only perform the DOM operation once', inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.base-class', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.removeClass(element, 'base-class one two'); //still true since we're before the reflow expect(element.hasClass('base-class')).toBe(false); //this will cancel the remove animation $animate.addClass(element, 'base-class one two'); //the cancellation was a success and the class was added right away //since there was no successive animation for the after animation expect(element.hasClass('base-class')).toBe(false); //the reflow... $animate.triggerReflow(); //the reflow DOM operation was commenced but it ran before so it //shouldn't run agaun expect(element.hasClass('base-class')).toBe(true); })); it('should block and unblock transitions before the dom operation occurs', inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) { if (!$sniffer.transitions) return; $animate.enabled(true); ss.addRule('.cross-animation', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); var capturedProperty = 'none'; var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var node = element[0]; node._setAttribute = node.setAttribute; node.setAttribute = function(prop, val) { if(prop == 'class' && val.indexOf('trigger-class') >= 0) { var propertyKey = ($sniffer.vendorPrefix == 'Webkit' ? '-webkit-' : '') + 'transition-property'; capturedProperty = element.css(propertyKey); } node._setAttribute(prop, val); }; expect(capturedProperty).toBe('none'); $animate.addClass(element, 'trigger-class'); $animate.triggerReflow(); expect(capturedProperty).not.toBe('none'); })); it('should block and unblock keyframe animations around the reflow operation', inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) { if (!$sniffer.animations) return; $animate.enabled(true); ss.addRule('.cross-animation', '-webkit-animation:1s my_animation;' + 'animation:1s my_animation;'); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var node = element[0]; var animationKey = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation'; $animate.addClass(element, 'trigger-class'); expect(node.style[animationKey]).toContain('none'); $animate.triggerReflow(); expect(node.style[animationKey]).not.toContain('none'); })); it('should block and unblock keyframe animations before the followup JS animation occurs', function() { module(function($animateProvider) { $animateProvider.register('.special', function($sniffer, $window) { var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation'; return { beforeAddClass : function(element, className, done) { expect(element[0].style[prop]).not.toContain('none'); expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('1s'); done(); }, addClass : function(element, className, done) { expect(element[0].style[prop]).not.toContain('none'); expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('1s'); done(); } } }); }); inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout, $window) { if (!$sniffer.animations) return; $animate.enabled(true); ss.addRule('.special', '-webkit-animation:1s special_animation;' + 'animation:1s special_animation;'); var capturedProperty = 'none'; var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.addClass(element, 'some-klass'); var prop = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation'; expect(element[0].style[prop]).toContain('none'); expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('0s'); $animate.triggerReflow(); }); }); it('should round up long elapsedTime values to close off a CSS3 animation', inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout, $window) { if (!$sniffer.animations) return; ss.addRule('.millisecond-transition.ng-leave', '-webkit-transition:510ms linear all;' + 'transition:510ms linear all;'); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $animate.leave(element); $rootScope.$digest(); $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0.50999999991 }); expect($rootElement.children().length).toBe(0); })); it('should properly animate elements with compound directives', function() { var capturedAnimation; module(function($animateProvider) { $animateProvider.register('.special', function() { return { enter : function(element, done) { capturedAnimation = 'enter'; done(); }, leave : function(element, done) { capturedAnimation = 'leave'; done(); } } }); }); inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer, $animate) { if(!$sniffer.transitions) return; $templateCache.put('item-template', 'item: #{{ item }} '); var element = $compile('
                      ' + '
                      ' + '
                      ')($rootScope); ss.addRule('.special', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); $rootElement.append(element); jqLite($document[0].body).append($rootElement); $rootScope.tpl = 'item-template'; $rootScope.items = [1,2,3]; $rootScope.$digest(); $animate.triggerReflow(); expect(capturedAnimation).toBe('enter'); expect(element.text()).toContain('item: #1'); forEach(element.children(), function(kid) { browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); }); $timeout.flush(); $rootScope.items = []; $rootScope.$digest(); $animate.triggerReflow(); expect(capturedAnimation).toBe('leave'); }); }); it('should animate only the specified CSS className', function() { var captures = {}; module(function($animateProvider) { $animateProvider.classNameFilter(/prefixed-animation/); $animateProvider.register('.capture', function() { return { enter : buildFn('enter'), leave : buildFn('leave') }; function buildFn(key) { return function(element, className, done) { captures[key] = true; (done || className)(); } } }); }); inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer, $animate) { if(!$sniffer.transitions) return; var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var enterDone = false; $animate.enter(element, $rootElement, null, function() { enterDone = true; }); $rootScope.$digest(); $timeout.flush(); expect(captures['enter']).toBeUndefined(); expect(enterDone).toBe(true); element.addClass('prefixed-animation'); var leaveDone = false; $animate.leave(element, function() { leaveDone = true; }); $rootScope.$digest(); $timeout.flush(); expect(captures['leave']).toBe(true); expect(leaveDone).toBe(true); }); }); it('should respect the most relevant CSS transition property if defined in multiple classes', inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.base-class', '-webkit-transition:1s linear all;' + 'transition:1s linear all;'); ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' + 'transition:5s linear all;'); $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var ready = false; $animate.addClass(element, 'on', function() { ready = true; }); $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 }); $timeout.flush(1); expect(ready).toBe(false); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 5 }); $timeout.flush(1); expect(ready).toBe(true); ready = false; $animate.removeClass(element, 'on', function() { ready = true; }); $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 }); $timeout.flush(1); expect(ready).toBe(true); })); it('should not apply a transition upon removal of a class that has a transition', inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' + 'transition:5s linear all;'); $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var ready = false; $animate.removeClass(element, 'on', function() { ready = true; }); $timeout.flush(1); expect(ready).toBe(true); })); it('should avoid skip animations if the same CSS class is added / removed synchronously before the reflow kicks in', inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) { if (!$sniffer.transitions) return; ss.addRule('.water-class', '-webkit-transition:2s linear all;' + 'transition:2s linear all;'); $animate.enabled(true); var element = $compile('
                      ')($rootScope); $rootElement.append(element); jqLite($document[0].body).append($rootElement); var signature = ''; $animate.removeClass(element, 'on', function() { signature += 'A'; }); $animate.addClass(element, 'on', function() { signature += 'B'; }); $timeout.flush(1); expect(signature).toBe('AB'); signature = ''; $animate.removeClass(element, 'on', function() { signature += 'A'; }); $animate.addClass(element, 'on', function() { signature += 'B'; }); $animate.removeClass(element, 'on', function() { signature += 'C'; }); $timeout.flush(1); expect(signature).toBe('AB'); $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2000 }); $timeout.flush(1); expect(signature).toBe('ABC'); })); }); }); angular.js-1.2.11/test/ngCookies/000077500000000000000000000000001227375216300165505ustar00rootroot00000000000000angular.js-1.2.11/test/ngCookies/cookiesSpec.js000066400000000000000000000107541227375216300213640ustar00rootroot00000000000000'use strict'; describe('$cookies', function() { beforeEach(module('ngCookies', function($provide) { $provide.factory('$browser', function(){ return angular.extend(new angular.mock.$Browser(), {cookieHash: {preexisting:'oldCookie'}}); }); })); it('should provide access to existing cookies via object properties and keep them in sync', inject(function($cookies, $browser, $rootScope) { expect($cookies).toEqual({'preexisting': 'oldCookie'}); // access internal cookie storage of the browser mock directly to simulate behavior of // document.cookie $browser.cookieHash['brandNew'] = 'cookie'; $browser.poll(); expect($cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'}); $browser.cookieHash['brandNew'] = 'cookie2'; $browser.poll(); expect($cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'}); delete $browser.cookieHash['brandNew']; $browser.poll(); expect($cookies).toEqual({'preexisting': 'oldCookie'}); })); it('should create or update a cookie when a value is assigned to a property', inject(function($cookies, $browser, $rootScope) { $cookies.oatmealCookie = 'nom nom'; $rootScope.$digest(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); $cookies.oatmealCookie = 'gone'; $rootScope.$digest(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); })); it('should drop or reset any cookie that was set to a non-string value', inject(function($cookies, $browser, $rootScope) { $cookies.nonString = [1, 2, 3]; $cookies.nullVal = null; $cookies.undefVal = undefined; $cookies.preexisting = function() {}; $rootScope.$digest(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); expect($cookies).toEqual({'preexisting': 'oldCookie'}); })); it('should remove a cookie when a $cookies property is deleted', inject(function($cookies, $browser, $rootScope) { $cookies.oatmealCookie = 'nom nom'; $rootScope.$digest(); $browser.poll(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); delete $cookies.oatmealCookie; $rootScope.$digest(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); })); it('should drop or reset cookies that browser refused to store', inject(function($cookies, $browser, $rootScope) { var i, longVal; for (i=0; i<5000; i++) { longVal += '*'; } //drop if no previous value $cookies.longCookie = longVal; $rootScope.$digest(); expect($cookies).toEqual({'preexisting': 'oldCookie'}); //reset if previous value existed $cookies.longCookie = 'shortVal'; $rootScope.$digest(); expect($cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); $cookies.longCookie = longVal; $rootScope.$digest(); expect($cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); })); }); describe('$cookieStore', function() { beforeEach(module('ngCookies')); it('should serialize objects to json', inject(function($cookieStore, $browser, $rootScope) { $cookieStore.put('objectCookie', {id: 123, name: 'blah'}); $rootScope.$digest(); expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); })); it('should deserialize json to object', inject(function($cookieStore, $browser) { $browser.cookies('objectCookie', '{"id":123,"name":"blah"}'); $browser.poll(); expect($cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'}); })); it('should delete objects from the store when remove is called', inject(function($cookieStore, $browser, $rootScope) { $cookieStore.put('gonner', { "I'll":"Be Back"}); $rootScope.$digest(); //force eval in test $browser.poll(); expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); $cookieStore.remove('gonner'); $rootScope.$digest(); expect($browser.cookies()).toEqual({}); })); it('should handle empty string value cookies', inject(function ($cookieStore, $browser, $rootScope) { $cookieStore.put("emptyCookie",''); $rootScope.$digest(); expect($browser.cookies()). toEqual({ 'emptyCookie': '""' }); expect($cookieStore.get("emptyCookie")).toEqual(''); $browser.cookieHash['blankCookie'] = ''; $browser.poll(); expect($cookieStore.get("blankCookie")).toEqual(''); })) }); angular.js-1.2.11/test/ngMock/000077500000000000000000000000001227375216300160455ustar00rootroot00000000000000angular.js-1.2.11/test/ngMock/angular-mocksSpec.js000066400000000000000000001331051227375216300217640ustar00rootroot00000000000000'use strict'; describe('ngMock', function() { var noop = angular.noop; describe('TzDate', function() { function minutes(min) { return min*60*1000; } it('should look like a Date', function() { var date = new angular.mock.TzDate(0,0); expect(angular.isDate(date)).toBe(true); }); it('should take millis as constructor argument', function() { expect(new angular.mock.TzDate(0, 0).getTime()).toBe(0); expect(new angular.mock.TzDate(0, 1283555108000).getTime()).toBe(1283555108000); }); it('should take dateString as constructor argument', function() { expect(new angular.mock.TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023); }); it('should fake getLocalDateString method', function() { //0 in -3h var t0 = new angular.mock.TzDate(-3, 0); expect(t0.toLocaleDateString()).toMatch('1970'); //0 in +0h var t1 = new angular.mock.TzDate(0, 0); expect(t1.toLocaleDateString()).toMatch('1970'); //0 in +3h var t2 = new angular.mock.TzDate(3, 0); expect(t2.toLocaleDateString()).toMatch('1969'); }); it('should fake toISOString method', function() { var date = new angular.mock.TzDate(-1, '2009-10-09T01:02:03.027Z'); if (new Date().toISOString) { expect(date.toISOString()).toEqual('2009-10-09T01:02:03.027Z'); } else { expect(date.toISOString).toBeUndefined(); } }); it('should fake getHours method', function() { //0 in -3h var t0 = new angular.mock.TzDate(-3, 0); expect(t0.getHours()).toBe(3); //0 in +0h var t1 = new angular.mock.TzDate(0, 0); expect(t1.getHours()).toBe(0); //0 in +3h var t2 = new angular.mock.TzDate(3, 0); expect(t2.getHours()).toMatch(21); }); it('should fake getMinutes method', function() { //0:15 in -3h var t0 = new angular.mock.TzDate(-3, minutes(15)); expect(t0.getMinutes()).toBe(15); //0:15 in -3.25h var t0a = new angular.mock.TzDate(-3.25, minutes(15)); expect(t0a.getMinutes()).toBe(30); //0 in +0h var t1 = new angular.mock.TzDate(0, minutes(0)); expect(t1.getMinutes()).toBe(0); //0:15 in +0h var t1a = new angular.mock.TzDate(0, minutes(15)); expect(t1a.getMinutes()).toBe(15); //0:15 in +3h var t2 = new angular.mock.TzDate(3, minutes(15)); expect(t2.getMinutes()).toMatch(15); //0:15 in +3.25h var t2a = new angular.mock.TzDate(3.25, minutes(15)); expect(t2a.getMinutes()).toMatch(0); }); it('should fake getSeconds method', function() { //0 in -3h var t0 = new angular.mock.TzDate(-3, 0); expect(t0.getSeconds()).toBe(0); //0 in +0h var t1 = new angular.mock.TzDate(0, 0); expect(t1.getSeconds()).toBe(0); //0 in +3h var t2 = new angular.mock.TzDate(3, 0); expect(t2.getSeconds()).toMatch(0); }); it('should fake getMilliseconds method', function() { expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.003Z').getMilliseconds()).toBe(3); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getMilliseconds()).toBe(23); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.123Z').getMilliseconds()).toBe(123); }); it('should create a date representing new year in Bratislava', function() { var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z'); expect(newYearInBratislava.getTimezoneOffset()).toBe(-60); expect(newYearInBratislava.getFullYear()).toBe(2010); expect(newYearInBratislava.getMonth()).toBe(0); expect(newYearInBratislava.getDate()).toBe(1); expect(newYearInBratislava.getHours()).toBe(0); expect(newYearInBratislava.getMinutes()).toBe(0); expect(newYearInBratislava.getSeconds()).toBe(0); }); it('should delegate all the UTC methods to the original UTC Date object', function() { //from when created from string var date1 = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z'); expect(date1.getUTCFullYear()).toBe(2009); expect(date1.getUTCMonth()).toBe(11); expect(date1.getUTCDate()).toBe(31); expect(date1.getUTCHours()).toBe(23); expect(date1.getUTCMinutes()).toBe(0); expect(date1.getUTCSeconds()).toBe(0); //from when created from millis var date2 = new angular.mock.TzDate(-1, date1.getTime()); expect(date2.getUTCFullYear()).toBe(2009); expect(date2.getUTCMonth()).toBe(11); expect(date2.getUTCDate()).toBe(31); expect(date2.getUTCHours()).toBe(23); expect(date2.getUTCMinutes()).toBe(0); expect(date2.getUTCSeconds()).toBe(0); }); it('should throw error when no third param but toString called', function() { expect(function() { new angular.mock.TzDate(0,0).toString(); }). toThrow('Method \'toString\' is not implemented in the TzDate mock'); }); }); describe('$log', function() { angular.forEach([true, false], function(debugEnabled) { describe('debug ' + debugEnabled, function() { beforeEach(module(function($logProvider) { $logProvider.debugEnabled(debugEnabled); })); afterEach(inject(function($log){ $log.reset(); })); it("should skip debugging output if disabled (" + debugEnabled + ")", inject(function($log) { $log.log('fake log'); $log.info('fake log'); $log.warn('fake log'); $log.error('fake log'); $log.debug('fake log'); expect($log.log.logs).toContain(['fake log']); expect($log.info.logs).toContain(['fake log']); expect($log.warn.logs).toContain(['fake log']); expect($log.error.logs).toContain(['fake log']); if (debugEnabled) { expect($log.debug.logs).toContain(['fake log']); } else { expect($log.debug.logs).toEqual([]); } })); }); }); describe('debug enabled (default)', function() { var $log; beforeEach(inject(['$log', function(log) { $log = log; }])); afterEach(inject(function($log){ $log.reset(); })); it('should provide the log method', function() { expect(function() { $log.log(''); }).not.toThrow(); }); it('should provide the info method', function() { expect(function() { $log.info(''); }).not.toThrow(); }); it('should provide the warn method', function() { expect(function() { $log.warn(''); }).not.toThrow(); }); it('should provide the error method', function() { expect(function() { $log.error(''); }).not.toThrow(); }); it('should provide the debug method', function() { expect(function() { $log.debug(''); }).not.toThrow(); }); it('should store log messages', function() { $log.log('fake log'); expect($log.log.logs).toContain(['fake log']); }); it('should store info messages', function() { $log.info('fake log'); expect($log.info.logs).toContain(['fake log']); }); it('should store warn messages', function() { $log.warn('fake log'); expect($log.warn.logs).toContain(['fake log']); }); it('should store error messages', function() { $log.error('fake log'); expect($log.error.logs).toContain(['fake log']); }); it('should store debug messages', function() { $log.debug('fake log'); expect($log.debug.logs).toContain(['fake log']); }); it('should assertEmpty', function(){ try { $log.error(Error('MyError')); $log.warn(Error('MyWarn')); $log.info(Error('MyInfo')); $log.log(Error('MyLog')); $log.debug(Error('MyDebug')); $log.assertEmpty(); } catch (error) { error = error.message || error; expect(error).toMatch(/Error: MyError/m); expect(error).toMatch(/Error: MyWarn/m); expect(error).toMatch(/Error: MyInfo/m); expect(error).toMatch(/Error: MyLog/m); expect(error).toMatch(/Error: MyDebug/m); } finally { $log.reset(); } }); it('should reset state', function(){ $log.error(Error('MyError')); $log.warn(Error('MyWarn')); $log.info(Error('MyInfo')); $log.log(Error('MyLog')); $log.reset(); var passed = false; try { $log.assertEmpty(); // should not throw error! passed = true; } catch (e) { passed = e; } expect(passed).toBe(true); }); }); }); describe('$interval', function() { it('should run tasks repeatedly', inject(function($interval) { var counter = 0; $interval(function() { counter++; }, 1000); expect(counter).toBe(0); $interval.flush(1000); expect(counter).toBe(1); $interval.flush(1000); expect(counter).toBe(2); })); it('should call $apply after each task is executed', inject(function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(noop, 1000); expect(applySpy).not.toHaveBeenCalled(); $interval.flush(1000); expect(applySpy).toHaveBeenCalledOnce(); applySpy.reset(); $interval(noop, 1000); $interval(noop, 1000); $interval.flush(1000); expect(applySpy.callCount).toBe(3); })); it('should NOT call $apply if invokeApply is set to false', inject(function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(noop, 1000, 0, false); expect(applySpy).not.toHaveBeenCalled(); $interval.flush(2000); expect(applySpy).not.toHaveBeenCalled(); })); it('should allow you to specify the delay time', inject(function($interval) { var counter = 0; $interval(function() { counter++; }, 123); expect(counter).toBe(0); $interval.flush(122); expect(counter).toBe(0); $interval.flush(1); expect(counter).toBe(1); })); it('should allow you to specify a number of iterations', inject(function($interval) { var counter = 0; $interval(function() {counter++}, 1000, 2); $interval.flush(1000); expect(counter).toBe(1); $interval.flush(1000); expect(counter).toBe(2); $interval.flush(1000); expect(counter).toBe(2); })); describe('flush', function() { it('should move the clock forward by the specified time', inject(function($interval) { var counterA = 0; var counterB = 0; $interval(function() { counterA++; }, 100); $interval(function() { counterB++; }, 401); $interval.flush(200); expect(counterA).toEqual(2); $interval.flush(201); expect(counterA).toEqual(4); expect(counterB).toEqual(1); })); }); it('should return a promise which will be updated with the count on each iteration', inject(function($interval) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0', 'tick', 'promise update: 1']); })); it('should return a promise which will be resolved after the specified number of iterations', inject(function($interval) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000, 2); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $interval.flush(1000); expect(log).toEqual([ 'tick', 'promise update: 0', 'tick', 'promise update: 1', 'promise success: 2']); })); describe('exception handling', function() { beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); it('should delegate exception to the $exceptionHandler service', inject( function($interval, $exceptionHandler) { $interval(function() { throw "Test Error"; }, 1000); expect($exceptionHandler.errors).toEqual([]); $interval.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error"]); $interval.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error", "Test Error"]); })); it('should call $apply even if an exception is thrown in callback', inject( function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').andCallThrough(); $interval(function() { throw "Test Error"; }, 1000); expect(applySpy).not.toHaveBeenCalled(); $interval.flush(1000); expect(applySpy).toHaveBeenCalled(); })); it('should still update the interval promise when an exception is thrown', inject(function($interval) { var log = [], promise = $interval(function() { throw "Some Error"; }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); $interval.flush(1000); expect(log).toEqual(['promise update: 0']); })); }); describe('cancel', function() { it('should cancel tasks', inject(function($interval) { var task1 = jasmine.createSpy('task1', 1000), task2 = jasmine.createSpy('task2', 1000), task3 = jasmine.createSpy('task3', 1000), promise1, promise3; promise1 = $interval(task1, 200); $interval(task2, 1000); promise3 = $interval(task3, 333); $interval.cancel(promise3); $interval.cancel(promise1); $interval.flush(1000); expect(task1).not.toHaveBeenCalled(); expect(task2).toHaveBeenCalledOnce(); expect(task3).not.toHaveBeenCalled(); })); it('should cancel the promise', inject(function($interval, $rootScope) { var promise = $interval(noop, 1000), log = []; promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); $interval.cancel(promise); $interval.flush(1000); $rootScope.$apply(); // For resolving the promise - // necessary since q uses $rootScope.evalAsync. expect(log).toEqual(['promise update: 0', 'promise error: canceled']); })); it('should return true if a task was successfully canceled', inject(function($interval) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), promise1, promise2; promise1 = $interval(task1, 1000, 1); $interval.flush(1000); promise2 = $interval(task2, 1000, 1); expect($interval.cancel(promise1)).toBe(false); expect($interval.cancel(promise2)).toBe(true); })); it('should not throw a runtime exception when given an undefined promise', inject(function($interval) { expect($interval.cancel()).toBe(false); })); }); }); describe('defer', function() { var browser, log; beforeEach(inject(function($browser) { browser = $browser; log = ''; })); function logFn(text){ return function() { log += text +';'; }; } it('should flush', function() { browser.defer(logFn('A')); expect(log).toEqual(''); browser.defer.flush(); expect(log).toEqual('A;'); }); it('should flush delayed', function() { browser.defer(logFn('A')); browser.defer(logFn('B'), 10); browser.defer(logFn('C'), 20); expect(log).toEqual(''); expect(browser.defer.now).toEqual(0); browser.defer.flush(0); expect(log).toEqual('A;'); browser.defer.flush(); expect(log).toEqual('A;B;C;'); }); it('should defer and flush over time', function() { browser.defer(logFn('A'), 1); browser.defer(logFn('B'), 2); browser.defer(logFn('C'), 3); browser.defer.flush(0); expect(browser.defer.now).toEqual(0); expect(log).toEqual(''); browser.defer.flush(1); expect(browser.defer.now).toEqual(1); expect(log).toEqual('A;'); browser.defer.flush(2); expect(browser.defer.now).toEqual(3); expect(log).toEqual('A;B;C;'); }); it('should throw an exception if there is nothing to be flushed', function() { expect(function() {browser.defer.flush();}).toThrow('No deferred tasks to be flushed'); }); }); describe('$exceptionHandler', function() { it('should rethrow exceptions', inject(function($exceptionHandler) { expect(function() { $exceptionHandler('myException'); }).toThrow('myException'); })); it('should log exceptions', module(function($exceptionHandlerProvider){ $exceptionHandlerProvider.mode('log'); var $exceptionHandler = $exceptionHandlerProvider.$get(); $exceptionHandler('MyError'); expect($exceptionHandler.errors).toEqual(['MyError']); $exceptionHandler('MyError', 'comment'); expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']); })); it('should throw on wrong argument', module(function($exceptionHandlerProvider) { expect(function() { $exceptionHandlerProvider.mode('XXX'); }).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!"); })); }); describe('$timeout', function() { it('should expose flush method that will flush the pending queue of tasks', inject( function($timeout) { var logger = [], logFn = function(msg) { return function() { logger.push(msg) }}; $timeout(logFn('t1')); $timeout(logFn('t2'), 200); $timeout(logFn('t3')); expect(logger).toEqual([]); $timeout.flush(); expect(logger).toEqual(['t1', 't3', 't2']); })); it('should throw an exception when not flushed', inject(function($timeout){ $timeout(noop); var expectedError = 'Deferred tasks to flush (1): {id: 0, time: 0}'; expect(function() {$timeout.verifyNoPendingTasks();}).toThrow(expectedError); })); it('should do nothing when all tasks have been flushed', inject(function($timeout) { $timeout(noop); $timeout.flush(); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); })); it('should check against the delay if provided within timeout', inject(function($timeout) { $timeout(noop, 100); $timeout.flush(100); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); $timeout(noop, 1000); $timeout.flush(100); expect(function() {$timeout.verifyNoPendingTasks();}).toThrow(); $timeout.flush(900); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); })); it('should assert against the delay value', inject(function($timeout) { var count = 0; var iterate = function() { count++; }; $timeout(iterate, 100); $timeout(iterate, 123); $timeout.flush(100); expect(count).toBe(1); $timeout.flush(123); expect(count).toBe(2); })); }); describe('angular.mock.dump', function(){ var d = angular.mock.dump; it('should serialize primitive types', function(){ expect(d(undefined)).toEqual('undefined'); expect(d(1)).toEqual('1'); expect(d(null)).toEqual('null'); expect(d('abc')).toEqual('abc'); }); it('should serialize element', function(){ var e = angular.element('
                      abc
                      xyz'); expect(d(e).toLowerCase()).toEqual('
                      abc
                      xyz'); expect(d(e[0]).toLowerCase()).toEqual('
                      abc
                      '); }); it('should serialize scope', inject(function($rootScope){ $rootScope.obj = {abc:'123'}; expect(d($rootScope)).toMatch(/Scope\(.*\): \{/); expect(d($rootScope)).toMatch(/{"abc":"123"}/); })); it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope, $sniffer){ // MS IE8 just doesn't work for this kind of thing, since "for ... in" doesn't return // things like hasOwnProperty even if it is explicitly defined on the actual object! if ($sniffer.msie<=8) return; $rootScope.hasOwnProperty = 'X'; expect(d($rootScope)).toMatch(/Scope\(.*\): \{/); expect(d($rootScope)).toMatch(/hasOwnProperty: "X"/); })); }); describe('angular.mock.clearDataCache', function() { function keys(obj) { var keys = []; for(var key in obj) { if (obj.hasOwnProperty(key)) keys.push(key); } return keys.sort(); } function browserTrigger(element, eventType) { element = element[0]; if (document.createEvent) { var event = document.createEvent('MouseEvents'); event.initMouseEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); element.dispatchEvent(event); } else { element.fireEvent('on' + eventType); } } it('should remove data', function() { expect(angular.element.cache).toEqual({}); var div = angular.element('
                      '); div.data('name', 'angular'); expect(keys(angular.element.cache)).not.toEqual([]); angular.mock.clearDataCache(); expect(keys(angular.element.cache)).toEqual([]); }); it('should deregister event handlers', function() { expect(keys(angular.element.cache)).toEqual([]); var log = ''; var div = angular.element('
                      '); // crazy IE9 requires div to be connected to render DOM for click event to work // mousemove works even when not connected. This is a heisen-bug since stepping // through the code makes the test pass. Viva IE!!! angular.element(document.body).append(div) div.on('click', function() { log += 'click1;'}); div.on('click', function() { log += 'click2;'}); div.on('mousemove', function() { log += 'mousemove;'}); browserTrigger(div, 'click'); browserTrigger(div, 'mousemove'); expect(log).toEqual('click1;click2;mousemove;'); log = ''; angular.mock.clearDataCache(); browserTrigger(div, 'click'); browserTrigger(div, 'mousemove'); expect(log).toEqual(''); expect(keys(angular.element.cache)).toEqual([]); div.remove(); }); }); describe('jasmine module and inject', function(){ var log; beforeEach(function(){ log = ''; }); describe('module', function() { describe('object literal format', function() { var mock = { log: 'module' }; beforeEach(function() { module({ 'service': mock, 'other': { some: 'replacement'} }, 'ngResource', function ($provide) { $provide.value('example', 'win'); } ); }); it('should inject the mocked module', function() { inject(function(service) { expect(service).toEqual(mock); }); }); it('should support multiple key value pairs', function() { inject(function(service, other) { expect(other.some).toEqual('replacement'); expect(service).toEqual(mock); }); }); it('should integrate with string and function', function() { inject(function(service, $resource, example) { expect(service).toEqual(mock); expect($resource).toBeDefined(); expect(example).toEqual('win'); }); }); }); describe('in DSL', function() { it('should load module', module(function() { log += 'module'; })); afterEach(function() { inject(); expect(log).toEqual('module'); }); }); describe('inline in test', function() { it('should load module', function() { module(function() { log += 'module'; }); inject(); }); afterEach(function() { expect(log).toEqual('module'); }); }); }); describe('inject', function() { describe('in DSL', function() { it('should load module', inject(function() { log += 'inject'; })); afterEach(function() { expect(log).toEqual('inject'); }); }); describe('inline in test', function() { it('should load module', function() { inject(function() { log += 'inject'; }); }); afterEach(function() { expect(log).toEqual('inject'); }); }); describe('module with inject', function() { beforeEach(module(function(){ log += 'module;'; })); it('should inject', inject(function() { log += 'inject;'; })); afterEach(function() { expect(log).toEqual('module;inject;') }); }); // We don't run the following tests on IE8. // IE8 throws "Object does not support this property or method." error, // when thrown from a function defined on window (which `inject` is). it('should not change thrown Errors', inject(function($sniffer) { if ($sniffer.msie <= 8) return; expect(function() { inject(function() { throw new Error('test message'); }); }).toThrow('test message'); })); it('should not change thrown strings', inject(function($sniffer) { if ($sniffer.msie <= 8) return; expect(function() { inject(function() { throw 'test message'; }); }).toThrow('test message'); })); }); }); describe('$httpBackend', function() { var hb, callback, realBackendSpy; beforeEach(inject(function($httpBackend) { callback = jasmine.createSpy('callback'); hb = $httpBackend; })); it('should respond with first matched definition', function() { hb.when('GET', '/url1').respond(200, 'content', {}); hb.when('GET', '/url1').respond(201, 'another', {}); callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('content'); }); hb('GET', '/url1', null, callback); expect(callback).not.toHaveBeenCalled(); hb.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should respond with a copy of the mock data', function() { var mockObject = {a: 'b'}; hb.when('GET', '/url1').respond(200, mockObject, {}); callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toEqual({a: 'b'}); expect(response).not.toBe(mockObject); response.a = 'c'; }); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnce(); // Fire it again and verify that the returned mock data has not been // modified. callback.reset(); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(mockObject).toEqual({a: 'b'}); }); it('should throw error when unexpected request', function() { hb.when('GET', '/url1').respond(200, 'content'); expect(function() { hb('GET', '/xxx'); }).toThrow('Unexpected request: GET /xxx\nNo more request expected'); }); it('should match headers if specified', function() { hb.when('GET', '/url', null, {'X': 'val1'}).respond(201, 'content1'); hb.when('GET', '/url', null, {'X': 'val2'}).respond(202, 'content2'); hb.when('GET', '/url').respond(203, 'content3'); hb('GET', '/url', null, function(status, response) { expect(status).toBe(203); expect(response).toBe('content3'); }); hb('GET', '/url', null, function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }, {'X': 'val1'}); hb('GET', '/url', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }, {'X': 'val2'}); hb.flush(); }); it('should match data if specified', function() { hb.when('GET', '/a/b', '{a: true}').respond(201, 'content1'); hb.when('GET', '/a/b').respond(202, 'content2'); hb('GET', '/a/b', '{a: true}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }); hb.flush(); }); it('should match data object if specified', function() { hb.when('GET', '/a/b', {a: 1, b: 2}).respond(201, 'content1'); hb.when('GET', '/a/b').respond(202, 'content2'); hb('GET', '/a/b', '{"a":1,"b":2}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', '{"b":2,"a":1}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }); hb.flush(); }); it('should match only method', function() { hb.when('GET').respond(202, 'c'); callback.andCallFake(function(status, response) { expect(status).toBe(202); expect(response).toBe('c'); }); hb('GET', '/some', null, callback, {}); hb('GET', '/another', null, callback, {'X-Fake': 'Header'}); hb('GET', '/third', 'some-data', callback, {}); hb.flush(); expect(callback).toHaveBeenCalled(); }); it('should preserve the order of requests', function() { hb.when('GET', '/url1').respond(200, 'first'); hb.when('GET', '/url2').respond(201, 'second'); hb('GET', '/url2', null, callback); hb('GET', '/url1', null, callback); hb.flush(); expect(callback.callCount).toBe(2); expect(callback.argsForCall[0]).toEqual([201, 'second', '']); expect(callback.argsForCall[1]).toEqual([200, 'first', '']); }); describe('respond()', function() { it('should take values', function() { hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'}); hb('GET', '/url1', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val'); }); it('should take function', function() { hb.expect('GET', '/some').respond(function(m, u, d, h) { return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}]; }); hb('GET', '/some', 'data', callback, {a: 'b'}); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive'); }); it('should default status code to 200', function() { callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('some-data'); }); hb.expect('GET', '/url1').respond('some-data'); hb.expect('GET', '/url2').respond('some-data', {'X-Header': 'true'}); hb('GET', '/url1', null, callback); hb('GET', '/url2', null, callback); hb.flush(); expect(callback).toHaveBeenCalled(); expect(callback.callCount).toBe(2); }); it('should default response headers to ""', function() { hb.expect('GET', '/url1').respond(200, 'first'); hb.expect('GET', '/url2').respond('second'); hb('GET', '/url1', null, callback); hb('GET', '/url2', null, callback); hb.flush(); expect(callback.callCount).toBe(2); expect(callback.argsForCall[0]).toEqual([200, 'first', '']); expect(callback.argsForCall[1]).toEqual([200, 'second', '']); }); }); describe('expect()', function() { it('should require specified order', function() { hb.expect('GET', '/url1').respond(200, ''); hb.expect('GET', '/url2').respond(200, ''); expect(function() { hb('GET', '/url2', null, noop, {}); }).toThrow('Unexpected request: GET /url2\nExpected GET /url1'); }); it('should have precedence over when()', function() { callback.andCallFake(function(status, response) { expect(status).toBe(300); expect(response).toBe('expect'); }); hb.when('GET', '/url').respond(200, 'when'); hb.expect('GET', '/url').respond(300, 'expect'); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); }); it ('should throw exception when only headers differs from expectation', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'}); expect(function() { hb('GET', '/match', null, noop, {}); }).toThrow('Expected GET /match with different headers\n' + 'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}'); }); it ('should throw exception when only data differs from expectation', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', 'some-data'); expect(function() { hb('GET', '/match', 'different', noop, {}); }).toThrow('Expected GET /match with different data\n' + 'EXPECTED: some-data\nGOT: different'); }); it ('should not throw an exception when parsed body is equal to expected body object', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"a":1,"b":2}', noop, {}); }).not.toThrow(); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"b":2,"a":1}', noop, {}); }).not.toThrow(); }); it ('should throw exception when only parsed body differs from expected body object', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"a":1,"b":3}', noop, {}); }).toThrow('Expected GET /match with different data\n' + 'EXPECTED: {"a":1,"b":2}\nGOT: {"a":1,"b":3}'); }); it("should use when's respond() when no expect() respond is defined", function() { callback.andCallFake(function(status, response) { expect(status).toBe(201); expect(response).toBe('data'); }); hb.when('GET', '/some').respond(201, 'data'); hb.expect('GET', '/some'); hb('GET', '/some', null, callback); hb.flush(); expect(callback).toHaveBeenCalled(); expect(function() { hb.verifyNoOutstandingExpectation(); }).not.toThrow(); }); }); describe('flush()', function() { it('flush() should flush requests fired during callbacks', function() { hb.when('GET').respond(200, ''); hb('GET', '/some', null, function() { hb('GET', '/other', null, callback); }); hb.flush(); expect(callback).toHaveBeenCalled(); }); it('should flush given number of pending requests', function() { hb.when('GET').respond(200, ''); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb.flush(2); expect(callback).toHaveBeenCalled(); expect(callback.callCount).toBe(2); }); it('should throw exception when flushing more requests than pending', function() { hb.when('GET').respond(200, ''); hb('GET', '/url', null, callback); expect(function() {hb.flush(2);}).toThrow('No more pending request to flush !'); expect(callback).toHaveBeenCalledOnce(); }); it('should throw exception when no request to flush', function() { expect(function() {hb.flush();}).toThrow('No pending request to flush !'); hb.when('GET').respond(200, ''); hb('GET', '/some', null, callback); hb.flush(); expect(function() {hb.flush();}).toThrow('No pending request to flush !'); }); it('should throw exception if not all expectations satisfied', function() { hb.expect('GET', '/url1').respond(); hb.expect('GET', '/url2').respond(); hb('GET', '/url1', null, angular.noop); expect(function() {hb.flush();}).toThrow('Unsatisfied requests: GET /url2'); }); }); it('should abort requests when timeout promise resolves', function() { hb.expect('GET', '/url1').respond(200); var canceler, then = jasmine.createSpy('then').andCallFake(function(fn) { canceler = fn; }); hb('GET', '/url1', null, callback, null, {then: then}); expect(typeof canceler).toBe('function'); canceler(); // simulate promise resolution expect(callback).toHaveBeenCalledWith(-1, undefined, ''); hb.verifyNoOutstandingExpectation(); hb.verifyNoOutstandingRequest(); }); it('should throw an exception if no response defined', function() { hb.when('GET', '/test'); expect(function() { hb('GET', '/test', null, callback); }).toThrow('No response defined !'); }); it('should throw an exception if no response for exception and no definition', function() { hb.expect('GET', '/url'); expect(function() { hb('GET', '/url', null, callback); }).toThrow('No response defined !'); }); it('should respond undefined when JSONP method', function() { hb.when('JSONP', '/url1').respond(200); hb.expect('JSONP', '/url2').respond(200); expect(hb('JSONP', '/url1')).toBeUndefined(); expect(hb('JSONP', '/url2')).toBeUndefined(); }); it('should not have passThrough method', function() { expect(hb.passThrough).toBeUndefined(); }); describe('verifyExpectations', function() { it('should throw exception if not all expectations were satisfied', function() { hb.expect('POST', '/u1', 'ddd').respond(201, '', {}); hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb('POST', '/u1', 'ddd', noop, {}); expect(function() {hb.verifyNoOutstandingExpectation();}). toThrow('Unsatisfied requests: GET /u2, POST /u3'); }); it('should do nothing when no expectation', function() { hb.when('DELETE', '/some').respond(200, ''); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); it('should do nothing when all expectations satisfied', function() { hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb.when('DELETE', '/some').respond(200, ''); hb('GET', '/u2', noop); hb('POST', '/u3', noop); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); }); describe('verifyRequests', function() { it('should throw exception if not all requests were flushed', function() { hb.when('GET').respond(200); hb('GET', '/some', null, noop, {}); expect(function() { hb.verifyNoOutstandingRequest(); }).toThrow('Unflushed requests: 1'); }); }); describe('resetExpectations', function() { it('should remove all expectations', function() { hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb.resetExpectations(); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); it('should remove all pending responses', function() { var cancelledClb = jasmine.createSpy('cancelled'); hb.expect('GET', '/url').respond(200, ''); hb('GET', '/url', null, cancelledClb); hb.resetExpectations(); hb.expect('GET', '/url').respond(300, ''); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cancelledClb).not.toHaveBeenCalled(); }); it('should not remove definitions', function() { var cancelledClb = jasmine.createSpy('cancelled'); hb.when('GET', '/url').respond(200, 'success'); hb('GET', '/url', null, cancelledClb); hb.resetExpectations(); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cancelledClb).not.toHaveBeenCalled(); }); }); describe('expect/when shortcuts', function() { angular.forEach(['expect', 'when'], function(prefix) { angular.forEach(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'JSONP'], function(method) { var shortcut = prefix + method; it('should provide ' + shortcut + ' shortcut method', function() { hb[shortcut]('/foo').respond('bar'); hb(method, '/foo', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'bar', ''); }); }); }); }); describe('MockHttpExpectation', function() { it('should accept url as regexp', function() { var exp = new MockHttpExpectation('GET', /^\/x/); expect(exp.match('GET', '/x')).toBe(true); expect(exp.match('GET', '/xxx/x')).toBe(true); expect(exp.match('GET', 'x')).toBe(false); expect(exp.match('GET', 'a/x')).toBe(false); }); it('should accept data as regexp', function() { var exp = new MockHttpExpectation('POST', '/url', /\{.*?\}/); expect(exp.match('POST', '/url', '{"a": "aa"}')).toBe(true); expect(exp.match('POST', '/url', '{"one": "two"}')).toBe(true); expect(exp.match('POST', '/url', '{"one"')).toBe(false); }); it('should accept data as function', function() { var dataValidator = function(data) { var json = angular.fromJson(data); return !!json.id && json.status === 'N'; }; var exp = new MockHttpExpectation('POST', '/url', dataValidator); expect(exp.matchData({})).toBe(false); expect(exp.match('POST', '/url', '{"id": "xxx", "status": "N"}')).toBe(true); expect(exp.match('POST', '/url', {"id": "xxx", "status": "N"})).toBe(true); }); it('should ignore data only if undefined (not null or false)', function() { var exp = new MockHttpExpectation('POST', '/url', null); expect(exp.matchData(null)).toBe(true); expect(exp.matchData('some-data')).toBe(false); exp = new MockHttpExpectation('POST', '/url', undefined); expect(exp.matchData(null)).toBe(true); expect(exp.matchData('some-data')).toBe(true); }); it('should accept headers as function', function() { var exp = new MockHttpExpectation('GET', '/url', undefined, function(h) { return h['Content-Type'] == 'application/json'; }); expect(exp.matchHeaders({})).toBe(false); expect(exp.matchHeaders({'Content-Type': 'application/json', 'X-Another': 'true'})).toBe(true); }); }); }); describe('$rootElement', function() { it('should create mock application root', inject(function($rootElement) { expect($rootElement.text()).toEqual(''); })); }); }); describe('ngMockE2E', function() { describe('$httpBackend', function() { var hb, realHttpBackend, callback; beforeEach(function() { module(function($provide) { callback = jasmine.createSpy('callback'); realHttpBackend = jasmine.createSpy('real $httpBackend'); $provide.value('$httpBackend', realHttpBackend); $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); }); inject(function($injector) { hb = $injector.get('$httpBackend'); }); }); describe('passThrough()', function() { it('should delegate requests to the real backend when passThrough is invoked', function() { hb.when('GET', /\/passThrough\/.*/).passThrough(); hb('GET', '/passThrough/23', null, callback, {}, null, true); expect(realHttpBackend).toHaveBeenCalledOnceWith( 'GET', '/passThrough/23', null, callback, {}, null, true); }); }); describe('autoflush', function() { it('should flush responses via $browser.defer', inject(function($browser) { hb.when('GET', '/foo').respond('bar'); hb('GET', '/foo', null, callback); expect(callback).not.toHaveBeenCalled(); $browser.defer.flush(); expect(callback).toHaveBeenCalledOnce(); })); }); }); }); angular.js-1.2.11/test/ngResource/000077500000000000000000000000001227375216300167435ustar00rootroot00000000000000angular.js-1.2.11/test/ngResource/resourceSpec.js000066400000000000000000001203131227375216300217430ustar00rootroot00000000000000'use strict'; describe("resource", function() { var $resource, CreditCard, callback, $httpBackend; beforeEach(module('ngResource')); beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend'); $resource = $injector.get('$resource'); CreditCard = $resource('/CreditCard/:id:verb', {id:'@id.key'}, { charge:{ method:'post', params:{verb:'!charge'} }, patch: { method: 'PATCH' }, conditionalPut: { method: 'PUT', headers: { 'If-None-Match': '*' } } }); callback = jasmine.createSpy(); })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); }); describe('isValidDottedPath', function() { it('should support arbitrary dotted names', function() { expect(isValidDottedPath('')).toBe(false); expect(isValidDottedPath('1')).toBe(false); expect(isValidDottedPath('1abc')).toBe(false); expect(isValidDottedPath('.')).toBe(false); expect(isValidDottedPath('$')).toBe(true); expect(isValidDottedPath('a')).toBe(true); expect(isValidDottedPath('A')).toBe(true); expect(isValidDottedPath('a1')).toBe(true); expect(isValidDottedPath('$a')).toBe(true); expect(isValidDottedPath('$1')).toBe(true); expect(isValidDottedPath('$$')).toBe(true); expect(isValidDottedPath('$.$')).toBe(true); expect(isValidDottedPath('.$')).toBe(false); expect(isValidDottedPath('$.')).toBe(false); }); }); describe('lookupDottedPath', function() { var data = {a: {b: 'foo', c: null}}; it('should throw for invalid path', function() { expect(function() { lookupDottedPath(data, '.ckck') }).toThrowMinErr('$resource', 'badmember', 'Dotted member path "@.ckck" is invalid.'); }); it('should get dotted paths', function() { expect(lookupDottedPath(data, 'a')).toEqual({b: 'foo', c: null}); expect(lookupDottedPath(data, 'a.b')).toBe('foo'); expect(lookupDottedPath(data, 'a.c')).toBeNull(); }); it('should skip over null/undefined members', function() { expect(lookupDottedPath(data, 'a.b.c')).toBe(undefined); expect(lookupDottedPath(data, 'a.c.c')).toBe(undefined); expect(lookupDottedPath(data, 'a.b.c.d')).toBe(undefined); expect(lookupDottedPath(data, 'NOT_EXIST')).toBe(undefined); }); }); it('should not include a request body when calling $delete', function() { $httpBackend.expect('DELETE', '/fooresource', null).respond({}); var Resource = $resource('/fooresource'); var resource = new Resource({ foo: 'bar' }); resource.$delete(); $httpBackend.flush(); }); it("should build resource", function() { expect(typeof CreditCard).toBe('function'); expect(typeof CreditCard.get).toBe('function'); expect(typeof CreditCard.save).toBe('function'); expect(typeof CreditCard.remove).toBe('function'); expect(typeof CreditCard['delete']).toBe('function'); expect(typeof CreditCard.query).toBe('function'); }); it('should default to empty parameters', function() { $httpBackend.expect('GET', 'URL').respond({}); $resource('URL').query(); }); it('should ignore slashes of undefinend parameters', function() { var R = $resource('/Path/:a/:b/:c'); $httpBackend.when('GET', '/Path').respond('{}'); $httpBackend.when('GET', '/Path/0').respond('{}'); $httpBackend.when('GET', '/Path/false').respond('{}'); $httpBackend.when('GET', '/Path').respond('{}'); $httpBackend.when('GET', '/Path/').respond('{}'); $httpBackend.when('GET', '/Path/1').respond('{}'); $httpBackend.when('GET', '/Path/2/3').respond('{}'); $httpBackend.when('GET', '/Path/4/5').respond('{}'); $httpBackend.when('GET', '/Path/6/7/8').respond('{}'); R.get({}); R.get({a:0}); R.get({a:false}); R.get({a:null}); R.get({a:undefined}); R.get({a:''}); R.get({a:1}); R.get({a:2, b:3}); R.get({a:4, c:5}); R.get({a:6, b:7, c:8}); }); it('should not ignore leading slashes of undefinend parameters that have non-slash trailing sequence', function() { var R = $resource('/Path/:a.foo/:b.bar/:c.baz'); $httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/0.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/false.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/1.foo/.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/2.foo/3.bar.baz').respond('{}'); $httpBackend.when('GET', '/Path/4.foo/.bar/5.baz').respond('{}'); $httpBackend.when('GET', '/Path/6.foo/7.bar/8.baz').respond('{}'); R.get({}); R.get({a:0}); R.get({a:false}); R.get({a:null}); R.get({a:undefined}); R.get({a:''}); R.get({a:1}); R.get({a:2, b:3}); R.get({a:4, c:5}); R.get({a:6, b:7, c:8}); }); it('should not collapsed the url into an empty string', function() { var R = $resource('/:foo/:bar/'); $httpBackend.when('GET', '/').respond('{}'); R.get({}); }); it('should support escaping colons in url template', function() { var R = $resource('http://localhost\\:8080/Path/:a/\\:stillPath/:b'); $httpBackend.expect('GET', 'http://localhost:8080/Path/foo/:stillPath/bar').respond(); R.get({a: 'foo', b: 'bar'}); }); it('should support an unescaped url', function() { var R = $resource('http://localhost:8080/Path/:a'); $httpBackend.expect('GET', 'http://localhost:8080/Path/foo').respond(); R.get({a: 'foo'}); }); it('should correctly encode url params', function() { var R = $resource('/Path/:a'); $httpBackend.expect('GET', '/Path/foo%231').respond('{}'); $httpBackend.expect('GET', '/Path/doh!@foo?bar=baz%231').respond('{}'); $httpBackend.expect('GET', '/Path/herp$').respond('{}'); R.get({a: 'foo#1'}); R.get({a: 'doh!@foo', bar: 'baz#1'}); R.get({a: 'herp$'}); }); it('should not encode @ in url params', function() { //encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt //with regards to the character set (pchar) allowed in path segments //so we need this test to make sure that we don't over-encode the params and break stuff like //buzz api which uses @self var R = $resource('/Path/:a'); $httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}'); R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'}); }); it('should encode array params', function() { var R = $resource('/Path/:a'); $httpBackend.expect('GET', '/Path/doh&foo?bar=baz1&bar=baz2').respond('{}'); R.get({a: 'doh&foo', bar: ['baz1', 'baz2']}); }); it('should not encode string "null" to "+" in url params', function() { var R = $resource('/Path/:a'); $httpBackend.expect('GET', '/Path/null').respond('{}'); R.get({a: 'null'}); }); it('should allow relative paths in resource url', function () { var R = $resource(':relativePath'); $httpBackend.expect('GET', 'data.json').respond('{}'); R.get({ relativePath: 'data.json' }); }); it('should handle + in url params', function () { var R = $resource('/api/myapp/:myresource?from=:from&to=:to&histlen=:histlen'); $httpBackend.expect('GET', '/api/myapp/pear+apple?from=2012-04-01&to=2012-04-29&histlen=3').respond('{}'); R.get({ myresource: 'pear+apple', from : '2012-04-01', to : '2012-04-29', histlen : 3 }); }); it('should encode & in url params', function() { var R = $resource('/Path/:a'); $httpBackend.expect('GET', '/Path/doh&foo?bar=baz%261').respond('{}'); R.get({a: 'doh&foo', bar: 'baz&1'}); }); it('should build resource with default param', function() { $httpBackend.expect('GET', '/Order/123/Line/456.visa?minimum=0.05').respond({id: 'abc'}); var LineItem = $resource('/Order/:orderId/Line/:id:verb', {orderId: '123', id: '@id.key', verb:'.visa', minimum: 0.05}); var item = LineItem.get({id: 456}); $httpBackend.flush(); expect(item).toEqualData({id:'abc'}); }); it('should support @_property lookups with underscores', function() { $httpBackend.expect('GET', '/Order/123').respond({_id: {_key:'123'}, count: 0}); var LineItem = $resource('/Order/:_id', {_id: '@_id._key'}); var item = LineItem.get({_id: 123}); $httpBackend.flush(); expect(item).toEqualData({_id: {_key: '123'}, count: 0}); $httpBackend.expect('POST', '/Order/123').respond({_id: {_key:'123'}, count: 1}); item.$save(); $httpBackend.flush(); expect(item).toEqualData({_id: {_key: '123'}, count: 1}); }); it('should not pass default params between actions', function() { var R = $resource('/Path', {}, {get: {method: 'GET', params: {objId: '1'}}, perform: {method: 'GET'}}); $httpBackend.expect('GET', '/Path?objId=1').respond('{}'); $httpBackend.expect('GET', '/Path').respond('{}'); R.get({}); R.perform({}); }); it("should build resource with action default param overriding default param", function() { $httpBackend.expect('GET', '/Customer/123').respond({id: 'abc'}); var TypeItem = $resource('/:type/:typeId', {type: 'Order'}, {get: {method: 'GET', params: {type: 'Customer'}}}); var item = TypeItem.get({typeId: 123}); $httpBackend.flush(); expect(item).toEqualData({id: 'abc'}); }); it('should build resource with action default param reading the value from instance', function() { $httpBackend.expect('POST', '/Customer/123').respond(); var R = $resource('/Customer/:id', {}, {post: {method: 'POST', params: {id: '@id'}}}); var inst = new R({id:123}); expect(inst.id).toBe(123); inst.$post(); }); it('should not throw TypeError on null default params', function() { $httpBackend.expect('GET', '/Path?').respond('{}'); var R = $resource('/Path', {param: null}, {get: {method: 'GET'}}); expect(function() { R.get({}); }).not.toThrow(); }); it('should handle multiple params with same name', function() { var R = $resource('/:id/:id'); $httpBackend.when('GET').respond('{}'); $httpBackend.expect('GET', '/1/1'); R.get({id:1}); }); it('should throw an exception if a param is called "hasOwnProperty"', function() { expect(function() { $resource('/:hasOwnProperty').get(); }).toThrowMinErr('$resource','badname', "hasOwnProperty is not a valid parameter name"); }); it("should create resource", function() { $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123, name: 'misko'}); var cc = CreditCard.save({name: 'misko'}, callback); expect(cc).toEqualData({name: 'misko'}); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(cc).toEqualData({id: 123, name: 'misko'}); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual(cc); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it("should read resource", function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}, callback); expect(cc instanceof CreditCard).toBeTruthy(); expect(cc).toEqualData({}); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(cc).toEqualData({id: 123, number: '9876'}); expect(callback.mostRecentCall.args[0]).toEqual(cc); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it('should send correct headers', function() { $httpBackend.expectPUT('/CreditCard/123', undefined, function(headers) { return headers['If-None-Match'] == "*"; }).respond({id:123}); CreditCard.conditionalPut({id: {key:123}}); }); it("should read partial resource", function() { $httpBackend.expect('GET', '/CreditCard').respond([{id:{key:123}}]); var ccs = CreditCard.query(); $httpBackend.flush(); expect(ccs.length).toEqual(1); var cc = ccs[0]; expect(cc instanceof CreditCard).toBe(true); expect(cc.number).toBeUndefined(); $httpBackend.expect('GET', '/CreditCard/123').respond({id: {key: 123}, number: '9876'}); cc.$get(callback); $httpBackend.flush(); expect(callback.mostRecentCall.args[0]).toEqual(cc); expect(callback.mostRecentCall.args[1]()).toEqual({}); expect(cc.number).toEqual('9876'); }); it("should update resource", function() { $httpBackend.expect('POST', '/CreditCard/123', '{"id":{"key":123},"name":"misko"}'). respond({id: {key: 123}, name: 'rama'}); var cc = CreditCard.save({id: {key: 123}, name: 'misko'}, callback); expect(cc).toEqualData({id:{key:123}, name:'misko'}); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); }); it("should query resource", function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); var ccs = CreditCard.query({key: 'value'}, callback); expect(ccs).toEqualData([]); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(ccs).toEqualData([{id:1}, {id:2}]); expect(callback.mostRecentCall.args[0]).toEqual(ccs); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it("should have all arguments optional", function() { $httpBackend.expect('GET', '/CreditCard').respond([{id:1}]); var log = ''; var ccs = CreditCard.query(function() { log += 'cb;'; }); $httpBackend.flush(); expect(ccs).toEqualData([{id:1}]); expect(log).toEqual('cb;'); }); it('should delete resource and call callback', function() { $httpBackend.expect('DELETE', '/CreditCard/123').respond({}); CreditCard.remove({id:123}, callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(callback.mostRecentCall.args[0]).toEqualData({}); expect(callback.mostRecentCall.args[1]()).toEqual({}); callback.reset(); $httpBackend.expect('DELETE', '/CreditCard/333').respond(204, null); CreditCard.remove({id:333}, callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(callback.mostRecentCall.args[0]).toEqualData({}); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it('should post charge verb', function() { $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', '{"auth":"abc"}').respond({success: 'ok'}); CreditCard.charge({id:123, amount:10}, {auth:'abc'}, callback); }); it('should post charge verb on instance', function() { $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', '{"id":{"key":123},"name":"misko"}').respond({success: 'ok'}); var card = new CreditCard({id:{key:123}, name:'misko'}); card.$charge({amount:10}, callback); }); it("should patch a resource", function() { $httpBackend.expectPATCH('/CreditCard/123', '{"name":"igor"}'). respond({id: 123, name: 'rama'}); var card = CreditCard.patch({id: 123}, {name: 'igor'}, callback); expect(card).toEqualData({name: 'igor'}); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(callback).toHaveBeenCalled(); expect(card).toEqualData({id: 123, name: 'rama'}); }); it('should create on save', function() { $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123}, {header1: 'a'}); var cc = new CreditCard(); expect(cc.$get).toBeDefined(); expect(cc.$query).toBeDefined(); expect(cc.$remove).toBeDefined(); expect(cc.$save).toBeDefined(); cc.name = 'misko'; cc.$save(callback); expect(cc).toEqualData({name:'misko'}); $httpBackend.flush(); expect(cc).toEqualData({id:123}); expect(callback.mostRecentCall.args[0]).toEqual(cc); expect(callback.mostRecentCall.args[1]()).toEqual({header1: 'a'}); }); it('should not mutate the resource object if response contains no body', function() { var data = {id:{key:123}, number:'9876'}; $httpBackend.expect('GET', '/CreditCard/123').respond(data); var cc = CreditCard.get({id:123}); $httpBackend.flush(); expect(cc instanceof CreditCard).toBe(true); $httpBackend.expect('POST', '/CreditCard/123', angular.toJson(data)).respond(''); var idBefore = cc.id; cc.$save(); $httpBackend.flush(); expect(idBefore).toEqual(cc.id); }); it('should bind default parameters', function() { $httpBackend.expect('GET', '/CreditCard/123.visa?minimum=0.05').respond({id: 123}); var Visa = CreditCard.bind({verb:'.visa', minimum:0.05}); var visa = Visa.get({id:123}); $httpBackend.flush(); expect(visa).toEqualData({id:123}); }); it('should support dynamic default parameters (global)', function() { var currentGroup = 'students', Person = $resource('/Person/:group/:id', { group: function() { return currentGroup; }}); $httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'}); var fedor = Person.get({id: 'fedor'}); $httpBackend.flush(); expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'}); }); it('should support dynamic default parameters (action specific)', function() { var currentGroup = 'students', Person = $resource('/Person/:group/:id', {}, { fetch: {method: 'GET', params: {group: function() { return currentGroup; }}} }); $httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'}); var fedor = Person.fetch({id: 'fedor'}); $httpBackend.flush(); expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'}); }); it('should exercise full stack', function() { var Person = $resource('/Person/:id'); $httpBackend.expect('GET', '/Person/123').respond('\n{\n"name":\n"misko"\n}\n'); var person = Person.get({id:123}); $httpBackend.flush(); expect(person.name).toEqual('misko'); }); it('should return a resource instance when calling a class method with a resource instance', function() { $httpBackend.expect('GET', '/Person/123').respond('{"name":"misko"}'); var Person = $resource('/Person/:id'); var person = Person.get({id:123}); $httpBackend.flush(); $httpBackend.expect('POST', '/Person').respond('{"name":"misko2"}'); var person2 = Person.save(person); $httpBackend.flush(); expect(person2).toEqual(jasmine.any(Person)); }); describe('promise api', function() { var $rootScope; beforeEach(inject(function(_$rootScope_) { $rootScope = _$rootScope_; })); describe('single resource', function() { it('should add $promise to the result object', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); cc.$promise.then(callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe(cc); }); it('should keep $promise around after resolution', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); cc.$promise.then(callback); $httpBackend.flush(); callback.reset(); cc.$promise.then(callback); $rootScope.$apply(); //flush async queue expect(callback).toHaveBeenCalledOnce(); }); it('should keep the original promise after instance action', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); $httpBackend.expect('POST', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); var originalPromise = cc.$promise; cc.number = '666'; cc.$save({id: 123}); expect(cc.$promise).toBe(originalPromise); }); it('should allow promise chaining', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); cc.$promise.then(function(value) { return 'new value'; }).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnceWith('new value'); }); it('should allow $promise error callback registration', function() { $httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found'); var cc = CreditCard.get({id: 123}); cc.$promise.then(null, callback); $httpBackend.flush(); var response = callback.mostRecentCall.args[0]; expect(response.data).toEqual('resource not found'); expect(response.status).toEqual(404); }); it('should add $resolved boolean field to the result object', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); expect(cc.$resolved).toBe(false); cc.$promise.then(callback); expect(cc.$resolved).toBe(false); $httpBackend.flush(); expect(cc.$resolved).toBe(true); }); it('should set $resolved field to true when an error occurs', function() { $httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found'); var cc = CreditCard.get({id: 123}); cc.$promise.then(null, callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cc.$resolved).toBe(true); }); it('should keep $resolved true in all subsequent interactions', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = CreditCard.get({id: 123}); $httpBackend.flush(); expect(cc.$resolved).toBe(true); $httpBackend.expect('POST', '/CreditCard/123').respond(); cc.$save({id: 123}); expect(cc.$resolved).toBe(true); $httpBackend.flush(); expect(cc.$resolved).toBe(true); }); it('should return promise from action method calls', function() { $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); var cc = new CreditCard({name: 'Mojo'}); expect(cc).toEqualData({name: 'Mojo'}); cc.$get({id:123}).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cc).toEqualData({id: 123, number: '9876'}); callback.reset(); $httpBackend.expect('POST', '/CreditCard').respond({id: 1, number: '9'}); cc.$save().then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cc).toEqualData({id: 1, number: '9'}); }); it('should allow parsing a value from headers', function() { // https://github.com/angular/angular.js/pull/2607#issuecomment-17759933 $httpBackend.expect('POST', '/CreditCard').respond(201, '', {'Location': '/new-id'}); var parseUrlFromHeaders = function(response) { var resource = response.resource; resource.url = response.headers('Location'); return resource; }; var CreditCard = $resource('/CreditCard', {}, { save: { method: 'post', interceptor: {response: parseUrlFromHeaders} } }); var cc = new CreditCard({name: 'Me'}); cc.$save(); $httpBackend.flush(); expect(cc.url).toBe('/new-id'); }); it('should pass the same transformed value to success callbacks and to promises', function() { $httpBackend.expect('GET', '/CreditCard').respond(200, { value: 'original' }); var transformResponse = function (response) { return { value: 'transformed' }; }; var CreditCard = $resource('/CreditCard', {}, { call: { method: 'get', interceptor: { response: transformResponse } } }); var successValue, promiseValue; var cc = new CreditCard({ name: 'Me' }); var req = cc.$call({}, function (result) { successValue = result; }); req.then(function (result) { promiseValue = result; }); $httpBackend.flush(); expect(successValue).toEqual({ value: 'transformed' }); expect(promiseValue).toEqual({ value: 'transformed' }); expect(successValue).toBe(promiseValue); }); }); describe('resource collection', function() { it('should add $promise to the result object', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); var ccs = CreditCard.query({key: 'value'}); ccs.$promise.then(callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toBe(ccs); }); it('should keep $promise around after resolution', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); var ccs = CreditCard.query({key: 'value'}); ccs.$promise.then(callback); $httpBackend.flush(); callback.reset(); ccs.$promise.then(callback); $rootScope.$apply(); //flush async queue expect(callback).toHaveBeenCalledOnce(); }); it('should allow promise chaining', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); var ccs = CreditCard.query({key: 'value'}); ccs.$promise.then(function(value) { return 'new value'; }).then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnceWith('new value'); }); it('should allow $promise error callback registration', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found'); var ccs = CreditCard.query({key: 'value'}); ccs.$promise.then(null, callback); $httpBackend.flush(); var response = callback.mostRecentCall.args[0]; expect(response.data).toEqual('resource not found'); expect(response.status).toEqual(404); }); it('should add $resolved boolean field to the result object', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); var ccs = CreditCard.query({key: 'value'}, callback); expect(ccs.$resolved).toBe(false); ccs.$promise.then(callback); expect(ccs.$resolved).toBe(false); $httpBackend.flush(); expect(ccs.$resolved).toBe(true); }); it('should set $resolved field to true when an error occurs', function() { $httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found'); var ccs = CreditCard.query({key: 'value'}); ccs.$promise.then(null, callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); expect(ccs.$resolved).toBe(true); }); }); it('should allow per action response interceptor that gets full response', function() { CreditCard = $resource('/CreditCard', {}, { query: { method: 'get', isArray: true, interceptor: { response: function(response) { return response; } } } }); $httpBackend.expect('GET', '/CreditCard').respond([{id: 1}]); var ccs = CreditCard.query(); ccs.$promise.then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); var response = callback.mostRecentCall.args[0]; expect(response.resource).toBe(ccs); expect(response.status).toBe(200); expect(response.config).toBeDefined(); }); it('should allow per action responseError interceptor that gets full response', function() { CreditCard = $resource('/CreditCard', {}, { query: { method: 'get', isArray: true, interceptor: { responseError: function(response) { return response; } } } }); $httpBackend.expect('GET', '/CreditCard').respond(404); var ccs = CreditCard.query(); ccs.$promise.then(callback); $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); var response = callback.mostRecentCall.args[0]; expect(response.status).toBe(404); expect(response.config).toBeDefined(); }); }); describe('failure mode', function() { var ERROR_CODE = 500, ERROR_RESPONSE = 'Server Error', errorCB; beforeEach(function() { errorCB = jasmine.createSpy('error').andCallFake(function(response) { expect(response.data).toBe(ERROR_RESPONSE); expect(response.status).toBe(ERROR_CODE); }); }); it('should call the error callback if provided on non 2xx response', function() { $httpBackend.expect('GET', '/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE); CreditCard.get({id:123}, callback, errorCB); $httpBackend.flush(); expect(errorCB).toHaveBeenCalledOnce(); expect(callback).not.toHaveBeenCalled(); }); it('should call the error callback if provided on non 2xx response (without data)', function() { $httpBackend.expect('GET', '/CreditCard').respond(ERROR_CODE, ERROR_RESPONSE); CreditCard.get(callback, errorCB); $httpBackend.flush(); expect(errorCB).toHaveBeenCalledOnce(); expect(callback).not.toHaveBeenCalled(); }); }); it('should transform request/response', function() { var Person = $resource('/Person/:id', {}, { save: { method: 'POST', params: {id: '@id'}, transformRequest: function(data) { return angular.toJson({ __id: data.id }); }, transformResponse: function(data) { return { id: data.__id }; } } }); $httpBackend.expect('POST', '/Person/123', { __id: 123 }).respond({ __id: 456 }); var person = new Person({id:123}); person.$save(); $httpBackend.flush(); expect(person.id).toEqual(456); }); describe('suffix parameter', function() { describe('query', function() { it('should add a suffix', function() { $httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]); var UserService = $resource('/users/:id.json', {id: '@id'}); var user = UserService.query(); $httpBackend.flush(); expect(user).toEqualData([{id: 1, name: 'user1'}]); }); it('should not require it if not provided', function(){ $httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]); var UserService = $resource('/users.json'); var user = UserService.query(); $httpBackend.flush(); expect(user).toEqualData([{id: 1, name: 'user1'}]); }); it('should work when query parameters are supplied', function() { $httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]); var UserService = $resource('/users/:user_id.json', {user_id: '@id'}); var user = UserService.query({red: 'blue'}); $httpBackend.flush(); expect(user).toEqualData([{id: 1, name: 'user1'}]); }); it('should work when query parameters are supplied and the format is a resource parameter', function() { $httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]); var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'}); var user = UserService.query({red: 'blue'}); $httpBackend.flush(); expect(user).toEqualData([{id: 1, name: 'user1'}]); }); it('should work with the action is overriden', function(){ $httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]); var UserService = $resource('/users/:user_id', {user_id: '@id'}, { query: { method: 'GET', url: '/users/:user_id.json', isArray: true } }); var user = UserService.query(); $httpBackend.flush(); expect(user).toEqualData([ {id: 1, name: 'user1'} ]); }); }); describe('get', function(){ it('should add them to the id', function() { $httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'}); var UserService = $resource('/users/:user_id.json', {user_id: '@id'}); var user = UserService.get({user_id: 1}); $httpBackend.flush(); expect(user).toEqualData({id: 1, name: 'user1'}); }); it('should work when an id and query parameters are supplied', function() { $httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'}); var UserService = $resource('/users/:user_id.json', {user_id: '@id'}); var user = UserService.get({user_id: 1, red: 'blue'}); $httpBackend.flush(); expect(user).toEqualData({id: 1, name: 'user1'}); }); it('should work when the format is a parameter', function() { $httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'}); var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'}); var user = UserService.get({user_id: 1, red: 'blue'}); $httpBackend.flush(); expect(user).toEqualData({id: 1, name: 'user1'}); }); it('should work with the action is overriden', function(){ $httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'}); var UserService = $resource('/users/:user_id', {user_id: '@id'}, { get: { method: 'GET', url: '/users/:user_id.json' } }); var user = UserService.get({user_id: 1}); $httpBackend.flush(); expect(user).toEqualData({id: 1, name: 'user1'}); }); }); describe("save", function() { it('should append the suffix', function() { $httpBackend.expect('POST', '/users.json', '{"name":"user1"}').respond({id: 123, name: 'user1'}); var UserService = $resource('/users/:user_id.json', {user_id: '@id'}); var user = UserService.save({name: 'user1'}, callback); expect(user).toEqualData({name: 'user1'}); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(user).toEqualData({id: 123, name: 'user1'}); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual(user); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it('should append when an id is supplied', function() { $httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'}); var UserService = $resource('/users/:user_id.json', {user_id: '@id'}); var user = UserService.save({id: 123, name: 'newName'}, callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(user).toEqualData({id: 123, name: 'newName'}); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual(user); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); it('should append when an id is supplied and the format is a parameter', function() { $httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'}); var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'}); var user = UserService.save({id: 123, name: 'newName'}, callback); expect(callback).not.toHaveBeenCalled(); $httpBackend.flush(); expect(user).toEqualData({id: 123, name: 'newName'}); expect(callback).toHaveBeenCalledOnce(); expect(callback.mostRecentCall.args[0]).toEqual(user); expect(callback.mostRecentCall.args[1]()).toEqual({}); }); }); describe('escaping /. with /\\.', function() { it('should work with query()', function() { $httpBackend.expect('GET', '/users/.json').respond(); $resource('/users/\\.json').query(); }); it('should work with get()', function() { $httpBackend.expect('GET', '/users/.json').respond(); $resource('/users/\\.json').get(); }); it('should work with save()', function() { $httpBackend.expect('POST', '/users/.json').respond(); $resource('/users/\\.json').save({}); }); }); }); describe('action-level url override', function() { it('should support overriding url template with static url', function() { $httpBackend.expect('GET', '/override-url?type=Customer&typeId=123').respond({id: 'abc'}); var TypeItem = $resource('/:type/:typeId', {type: 'Order'}, { get: { method: 'GET', params: {type: 'Customer'}, url: '/override-url' } }); var item = TypeItem.get({typeId: 123}); $httpBackend.flush(); expect(item).toEqualData({id: 'abc'}); }); it('should support overriding url template with a new template ending in param', function() { // url parameter in action, parameter ending the string $httpBackend.expect('GET', '/Customer/123').respond({id: 'abc'}); var TypeItem = $resource('/foo/:type', {type: 'Order'}, { get: { method: 'GET', params: {type: 'Customer'}, url: '/:type/:typeId' } }); var item = TypeItem.get({typeId: 123}); $httpBackend.flush(); expect(item).toEqualData({id: 'abc'}); // url parameter in action, parameter not ending the string $httpBackend.expect('GET', '/Customer/123/pay').respond({id: 'abc'}); var TypeItem = $resource('/foo/:type', {type: 'Order'}, { get: { method: 'GET', params: {type: 'Customer'}, url: '/:type/:typeId/pay' } }); var item = TypeItem.get({typeId: 123}); $httpBackend.flush(); expect(item).toEqualData({id: 'abc'}); }); it('should support overriding url template with a new template ending in string', function() { $httpBackend.expect('GET', '/Customer/123/pay').respond({id: 'abc'}); var TypeItem = $resource('/foo/:type', {type: 'Order'}, { get: { method: 'GET', params: {type: 'Customer'}, url: '/:type/:typeId/pay' } }); var item = TypeItem.get({typeId: 123}); $httpBackend.flush(); expect(item).toEqualData({id: 'abc'}); }); }); }); describe('resource', function() { var $httpBackend, $resource; beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); beforeEach(module('ngResource')); beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend'); $resource = $injector.get('$resource'); })); it('should fail if action expects an object but response is an array', function() { var successSpy = jasmine.createSpy('successSpy'); var failureSpy = jasmine.createSpy('failureSpy'); $httpBackend.expect('GET', '/Customer/123').respond({id: 'abc'}); $resource('/Customer/123').query() .$promise.then(successSpy, function(e) { failureSpy(e.message); }); $httpBackend.flush(); expect(successSpy).not.toHaveBeenCalled(); expect(failureSpy).toHaveBeenCalled(); expect(failureSpy.mostRecentCall.args[0]).toMatch( /^\[\$resource:badcfg\] Error in resource configuration\. Expected response to contain an array but got an object/ ); }); it('should fail if action expects an array but response is an object', function() { var successSpy = jasmine.createSpy('successSpy'); var failureSpy = jasmine.createSpy('failureSpy'); $httpBackend.expect('GET', '/Customer/123').respond([1,2,3]); $resource('/Customer/123').get() .$promise.then(successSpy, function(e) { failureSpy(e.message); }); $httpBackend.flush(); expect(successSpy).not.toHaveBeenCalled(); expect(failureSpy).toHaveBeenCalled(); expect(failureSpy.mostRecentCall.args[0]).toMatch( /^\[\$resource:badcfg\] Error in resource configuration. Expected response to contain an object but got an array/ ) }); }); angular.js-1.2.11/test/ngRoute/000077500000000000000000000000001227375216300162525ustar00rootroot00000000000000angular.js-1.2.11/test/ngRoute/directive/000077500000000000000000000000001227375216300202305ustar00rootroot00000000000000angular.js-1.2.11/test/ngRoute/directive/ngViewSpec.js000066400000000000000000000706011227375216300226440ustar00rootroot00000000000000'use strict'; describe('ngView', function() { var element; beforeEach(module('ngRoute')); beforeEach(module(function($provide) { return function($rootScope, $compile, $animate) { element = $compile('
                      ')($rootScope); }; })); afterEach(function(){ dealoc(element); }); it('should do nothing when no routes are defined', inject(function($rootScope, $compile, $location) { $location.path('/unknown'); $rootScope.$digest(); expect(element.text()).toEqual(''); })); it('should instantiate controller after compiling the content', function() { var log = [], controllerScope, Ctrl = function($scope) { controllerScope = $scope; log.push('ctrl-init'); }; module(function($compileProvider, $routeProvider) { $compileProvider.directive('compileLog', function() { return { compile: function() { log.push('compile'); } }; }); $routeProvider.when('/some', {templateUrl: '/tpl.html', controller: Ctrl}); }); inject(function($route, $rootScope, $templateCache, $location) { $templateCache.put('/tpl.html', [200, '
                      partial
                      ', {}]); $location.path('/some'); $rootScope.$digest(); expect(controllerScope.$parent).toBe($rootScope); expect(controllerScope).toBe($route.current.scope); expect(log).toEqual(['compile', 'ctrl-init']); }); }); it('should instantiate controller for empty template', function() { var log = [], controllerScope, Ctrl = function($scope) { controllerScope = $scope; log.push('ctrl-init'); }; module(function($routeProvider) { $routeProvider.when('/some', {templateUrl: '/tpl.html', controller: Ctrl}); }); inject(function($route, $rootScope, $templateCache, $location) { $templateCache.put('/tpl.html', [200, '', {}]); $location.path('/some'); $rootScope.$digest(); expect(controllerScope.$parent).toBe($rootScope); expect(controllerScope).toBe($route.current.scope); expect(log).toEqual(['ctrl-init']); }); }); it('should instantiate controller with an alias', function() { var log = [], controllerScope, Ctrl = function($scope) { this.name = 'alias'; controllerScope = $scope; }; module(function($compileProvider, $routeProvider) { $routeProvider.when('/some', {templateUrl: '/tpl.html', controller: Ctrl, controllerAs: 'ctrl'}); }); inject(function($route, $rootScope, $templateCache, $location) { $templateCache.put('/tpl.html', [200, '
                      ', {}]); $location.path('/some'); $rootScope.$digest(); expect(controllerScope.ctrl.name).toBe('alias'); }); }); it('should support string controller declaration', function() { var MyCtrl = jasmine.createSpy('MyCtrl'); module(function($controllerProvider, $routeProvider) { $controllerProvider.register('MyCtrl', ['$scope', MyCtrl]); $routeProvider.when('/foo', {controller: 'MyCtrl', templateUrl: '/tpl.html'}); }); inject(function($route, $location, $rootScope, $templateCache) { $templateCache.put('/tpl.html', [200, '
                      ', {}]); $location.path('/foo'); $rootScope.$digest(); expect($route.current.controller).toBe('MyCtrl'); expect(MyCtrl).toHaveBeenCalledWith(element.children().scope()); }); }); it('should load content via xhr when route changes', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'myUrl1'}); $routeProvider.when('/bar', {templateUrl: 'myUrl2'}); }); inject(function($rootScope, $compile, $httpBackend, $location, $route) { expect(element.text()).toEqual(''); $location.path('/foo'); $httpBackend.expect('GET', 'myUrl1').respond('
                      {{1+3}}
                      '); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('4'); $location.path('/bar'); $httpBackend.expect('GET', 'myUrl2').respond('angular is da best'); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('angular is da best'); }); }); it('should use inline content route changes', function() { module(function($routeProvider) { $routeProvider.when('/foo', {template: '
                      {{1+3}}
                      '}); $routeProvider.when('/bar', {template: 'angular is da best'}); $routeProvider.when('/blank', {template: ''}); }); inject(function($rootScope, $compile, $location, $route) { expect(element.text()).toEqual(''); $location.path('/foo'); $rootScope.$digest(); expect(element.text()).toEqual('4'); $location.path('/bar'); $rootScope.$digest(); expect(element.text()).toEqual('angular is da best'); $location.path('/blank'); $rootScope.$digest(); expect(element.text()).toEqual(''); }); }); it('should remove all content when location changes to an unknown route', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'myUrl1'}); }); inject(function($rootScope, $compile, $location, $httpBackend, $route) { $location.path('/foo'); $httpBackend.expect('GET', 'myUrl1').respond('
                      {{1+3}}
                      '); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('4'); $location.path('/unknown'); $rootScope.$digest(); expect(element.text()).toEqual(''); }); }); it('should chain scopes and propagate evals to the child scope', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'myUrl1'}); }); inject(function($rootScope, $compile, $location, $httpBackend, $route) { $rootScope.parentVar = 'parent'; $location.path('/foo'); $httpBackend.expect('GET', 'myUrl1').respond('
                      {{parentVar}}
                      '); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('parent'); $rootScope.parentVar = 'new parent'; $rootScope.$digest(); expect(element.text()).toEqual('new parent'); }); }); it('should be possible to nest ngView in ngInclude', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'viewPartial.html'}); }); inject(function($httpBackend, $location, $route, $compile, $rootScope) { $httpBackend.whenGET('includePartial.html').respond('view: '); $httpBackend.whenGET('viewPartial.html').respond('content'); $location.path('/foo'); var elm = $compile( '
                      ' + 'include: ' + '
                      ')($rootScope); $rootScope.$digest(); $httpBackend.flush(); expect(elm.text()).toEqual('include: view: content'); expect($route.current.templateUrl).toEqual('viewPartial.html'); dealoc(elm) }); }); it('should initialize view template after the view controller was initialized even when ' + 'templates were cached', function() { //this is a test for a regression that was introduced by making the ng-view cache sync function ParentCtrl($scope) { $scope.log.push('parent'); } module(function($routeProvider) { $routeProvider.when('/foo', {controller: ParentCtrl, templateUrl: 'viewPartial.html'}); }); inject(function($rootScope, $compile, $location, $httpBackend, $route) { $rootScope.log = []; $rootScope.ChildCtrl = function($scope) { $scope.log.push('child'); }; $location.path('/foo'); $httpBackend.expect('GET', 'viewPartial.html'). respond('
                      ' + '
                      ' + '
                      '); $rootScope.$apply(); $httpBackend.flush(); expect($rootScope.log).toEqual(['parent', 'init', 'child']); $location.path('/'); $rootScope.$apply(); expect($rootScope.log).toEqual(['parent', 'init', 'child']); $rootScope.log = []; $location.path('/foo'); $rootScope.$apply(); expect($rootScope.log).toEqual(['parent', 'init', 'child']); }); }); it('should discard pending xhr callbacks if a new route is requested before the current ' + 'finished loading', function() { // this is a test for a bad race condition that affected feedback module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'myUrl1'}); $routeProvider.when('/bar', {templateUrl: 'myUrl2'}); }); inject(function($route, $rootScope, $location, $httpBackend) { expect(element.text()).toEqual(''); $location.path('/foo'); $httpBackend.expect('GET', 'myUrl1').respond('
                      {{1+3}}
                      '); $rootScope.$digest(); $location.path('/bar'); $httpBackend.expect('GET', 'myUrl2').respond('
                      {{1+1}}
                      '); $rootScope.$digest(); $httpBackend.flush(); // now that we have two requests pending, flush! expect(element.text()).toEqual('2'); }); }); it('should be async even if served from cache', function() { module(function($routeProvider) { $routeProvider.when('/foo', {controller: angular.noop, templateUrl: 'myUrl1'}); }); inject(function($route, $rootScope, $location, $templateCache) { $templateCache.put('myUrl1', [200, 'my partial', {}]); $location.path('/foo'); var called = 0; // we want to assert only during first watch $rootScope.$watch(function() { if (!called++) expect(element.text()).toBe(''); }); $rootScope.$digest(); expect(element.text()).toBe('my partial'); }); }); it('should fire $contentLoaded event when content compiled and linked', function() { var log = []; var logger = function(name) { return function() { log.push(name); }; }; var Ctrl = function($scope) { $scope.value = 'bound-value'; log.push('init-ctrl'); }; module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: Ctrl}); }); inject(function($templateCache, $rootScope, $location) { $rootScope.$on('$routeChangeStart', logger('$routeChangeStart')); $rootScope.$on('$routeChangeSuccess', logger('$routeChangeSuccess')); $rootScope.$on('$viewContentLoaded', logger('$viewContentLoaded')); $templateCache.put('tpl.html', [200, '{{value}}', {}]); $location.path('/foo'); $rootScope.$digest(); expect(element.text()).toBe('bound-value'); expect(log).toEqual([ '$routeChangeStart', 'init-ctrl', '$viewContentLoaded', '$routeChangeSuccess' ]); }); }); it('should destroy previous scope', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'tpl.html'}); }); inject(function($templateCache, $rootScope, $location) { $templateCache.put('tpl.html', [200, 'partial', {}]); expect($rootScope.$$childHead).toBeNull(); expect($rootScope.$$childTail).toBeNull(); $location.path('/foo'); $rootScope.$digest(); expect(element.text()).toBe('partial'); expect($rootScope.$$childHead).not.toBeNull(); expect($rootScope.$$childTail).not.toBeNull(); $location.path('/non/existing/route'); $rootScope.$digest(); expect(element.text()).toBe(''); expect($rootScope.$$childHead).toBeNull(); expect($rootScope.$$childTail).toBeNull(); }); }); it('should destroy previous scope if multiple route changes occur before server responds', function() { var log = []; var createCtrl = function(name) { return function($scope) { log.push('init-' + name); $scope.$on('$destroy', function() {log.push('destroy-' + name);}); }; }; module(function($routeProvider) { $routeProvider.when('/one', {templateUrl: 'one.html', controller: createCtrl('ctrl1')}); $routeProvider.when('/two', {templateUrl: 'two.html', controller: createCtrl('ctrl2')}); }); inject(function($httpBackend, $rootScope, $location) { $httpBackend.whenGET('one.html').respond('content 1'); $httpBackend.whenGET('two.html').respond('content 2'); $location.path('/one'); $rootScope.$digest(); $location.path('/two'); $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe('content 2'); expect(log).toEqual(['init-ctrl2']); $location.path('/non-existing'); $rootScope.$digest(); expect(element.text()).toBe(''); expect(log).toEqual(['init-ctrl2', 'destroy-ctrl2']); expect($rootScope.$$childHead).toBeNull(); expect($rootScope.$$childTail).toBeNull(); }); }); it('should $destroy scope after update and reload', function() { // this is a regression of bug, where $route doesn't copy scope when only updating var log = []; function logger(msg) { return function() { log.push(msg); }; } function createController(name) { return function($scope) { log.push('init-' + name); $scope.$on('$destroy', logger('destroy-' + name)); $scope.$on('$routeUpdate', logger('route-update')); }; } module(function($routeProvider) { $routeProvider.when('/bar', {templateUrl: 'tpl.html', controller: createController('bar')}); $routeProvider.when('/foo', { templateUrl: 'tpl.html', controller: createController('foo'), reloadOnSearch: false}); }); inject(function($templateCache, $location, $rootScope) { $templateCache.put('tpl.html', [200, 'partial', {}]); $location.url('/foo'); $rootScope.$digest(); expect(log).toEqual(['init-foo']); $location.search({q: 'some'}); $rootScope.$digest(); expect(log).toEqual(['init-foo', 'route-update']); $location.url('/bar'); $rootScope.$digest(); expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']); }); }); it('should evaluate onload expression after linking the content', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'tpl.html'}); }); inject(function($templateCache, $location, $rootScope) { $templateCache.put('tpl.html', [200, '{{1+1}}', {}]); $rootScope.load = jasmine.createSpy('onload'); $location.url('/foo'); $rootScope.$digest(); expect($rootScope.load).toHaveBeenCalledOnce(); }); }); it('should set $scope and $controllerController on the view elements (except for non-element nodes)', function() { function MyCtrl($scope) { $scope.state = 'WORKS'; $scope.ctrl = this; } module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: MyCtrl}); }); inject(function($templateCache, $location, $rootScope, $route) { // in the template the white-space before the div is an intentional non-element node, // a text might get wrapped into span so it's safer to just use white space $templateCache.put('tpl.html', [200, ' \n
                      {{state}}
                      ', {}]); $location.url('/foo'); $rootScope.$digest(); // using toMatch because in IE8+jquery the space doesn't get preserved. jquery bug? expect(element.text()).toMatch(/\s*WORKS/); var div = element.find('div'); expect(div.parent()[0].nodeName.toUpperCase()).toBeOneOf('NG:VIEW', 'VIEW'); expect(div.scope()).toBe($route.current.scope); expect(div.scope().hasOwnProperty('state')).toBe(true); expect(div.scope().state).toEqual('WORKS'); expect(div.controller()).toBe($route.current.scope.ctrl); }); }); it('should not set $scope or $controllerController on top level text elements in the view', function() { function MyCtrl($scope) {} module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: MyCtrl}); }); inject(function($templateCache, $location, $rootScope, $route) { $templateCache.put('tpl.html', '
                      '); $location.url('/foo'); $rootScope.$digest(); angular.forEach(element.contents(), function(node) { if(node.nodeType == 3 /* text node */) { expect(angular.element(node).scope()).not.toBe($route.current.scope); expect(angular.element(node).controller()).not.toBeDefined(); } else if(node.nodeType == 8 /* comment node */) { expect(angular.element(node).scope()).toBe(element.scope()); expect(angular.element(node).controller()).toBe(element.controller()); } else { expect(angular.element(node).scope()).toBe($route.current.scope); expect(angular.element(node).controller()).toBeDefined(); } }); }); }); }); describe('ngView and transcludes', function() { var element, directive; beforeEach(module('ngRoute', function($compileProvider) { element = null; directive = $compileProvider.directive; })); afterEach(function() { if (element) { dealoc(element); } }); it('should allow access to directive controller from children when used in a replace template', function() { var controller; module(function($routeProvider) { $routeProvider.when('/view', {templateUrl: 'view.html'}); directive('template', function() { return { template: '
                      ', replace: true, controller: function() { this.flag = true; } }; }); directive('test', function() { return { require: '^template', link: function(scope, el, attr, ctrl) { controller = ctrl; } }; }); }); inject(function($compile, $rootScope, $httpBackend, $location) { $httpBackend.expectGET('view.html').respond('
                      '); element = $compile('
                      ')($rootScope); $location.url('/view'); $rootScope.$apply(); $httpBackend.flush(); expect(controller.flag).toBe(true); }); }); it("should compile it's content correctly (although we remove it later)", function() { var testElement; module(function($compileProvider, $routeProvider) { $routeProvider.when('/view', {template: ' '}); var directive = $compileProvider.directive; directive('test', function() { return { link: function(scope, element) { testElement = element; } }; }); }); inject(function($compile, $rootScope, $location) { element = $compile('
                      ')($rootScope); $location.url('/view'); $rootScope.$apply(); expect(testElement[0].nodeName).toBe('DIV'); }); }); it('should link directives on the same element after the content has been loaded', function() { var contentOnLink; module(function($compileProvider, $routeProvider) { $routeProvider.when('/view', {template: 'someContent'}); $compileProvider.directive('test', function() { return { link: function(scope, element) { contentOnLink = element.text(); } }; }); }); inject(function($compile, $rootScope, $location) { element = $compile('
                      ')($rootScope); $location.url('/view'); $rootScope.$apply(); expect(contentOnLink).toBe('someContent'); }); }); it('should add the content to the element before compiling it', function() { var root; module(function($compileProvider, $routeProvider) { $routeProvider.when('/view', {template: ''}); $compileProvider.directive('test', function() { return { link: function(scope, element) { root = element.parent().parent(); } }; }); }); inject(function($compile, $rootScope, $location) { element = $compile('
                      ')($rootScope); $location.url('/view'); $rootScope.$apply(); expect(root[0]).toBe(element[0]); }); }); }); describe('ngView animations', function() { var body, element, $rootElement; beforeEach(module('ngRoute')); function html(html) { $rootElement.html(html); body.append($rootElement); element = $rootElement.children().eq(0); return element; } beforeEach(module(function() { // we need to run animation on attached elements; return function(_$rootElement_) { $rootElement = _$rootElement_; body = angular.element(document.body); }; })); afterEach(function(){ dealoc(body); dealoc(element); }); beforeEach(module(function($provide, $routeProvider) { $routeProvider.when('/foo', {controller: angular.noop, templateUrl: '/foo.html'}); $routeProvider.when('/bar', {controller: angular.noop, templateUrl: '/bar.html'}); return function($templateCache) { $templateCache.put('/foo.html', [200, '
                      data
                      ', {}]); $templateCache.put('/bar.html', [200, '
                      data2
                      ', {}]); } })); describe('hooks', function() { beforeEach(module('mock.animate')); it('should fire off the enter animation', inject(function($compile, $rootScope, $location, $animate) { element = $compile(html('
                      '))($rootScope); $location.path('/foo'); $rootScope.$digest(); var item = $animate.flushNext('enter').element; expect(item.text()).toBe('data'); })); it('should fire off the leave animation', inject(function($compile, $rootScope, $location, $templateCache, $animate) { var item; $templateCache.put('/foo.html', [200, '
                      foo
                      ', {}]); element = $compile(html('
                      '))($rootScope); $location.path('/foo'); $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('foo'); $location.path('/'); $rootScope.$digest(); item = $animate.flushNext('leave').element; expect(item.text()).toBe('foo'); })); it('should animate two separate ngView elements', inject(function($compile, $rootScope, $templateCache, $animate, $location) { var item; $rootScope.tpl = 'one'; element = $compile(html('
                      '))($rootScope); $rootScope.$digest(); $location.path('/foo'); $rootScope.$digest(); item = $animate.flushNext('enter').element; expect(item.text()).toBe('data'); $location.path('/bar'); $rootScope.$digest(); var itemA = $animate.flushNext('enter').element; expect(itemA).not.toEqual(itemB); var itemB = $animate.flushNext('leave').element; })); it('should render ngClass on ngView', inject(function($compile, $rootScope, $templateCache, $animate, $location, $timeout) { var item; $rootScope.tpl = 'one'; $rootScope.klass = 'classy'; element = $compile(html('
                      '))($rootScope); $rootScope.$digest(); $location.path('/foo'); $rootScope.$digest(); item = $animate.flushNext('enter').element; $animate.flushNext('addClass').element; expect(item.hasClass('classy')).toBe(true); $rootScope.klass = 'boring'; $rootScope.$digest(); $animate.flushNext('removeClass').element; $animate.flushNext('addClass').element; expect(item.hasClass('classy')).toBe(false); expect(item.hasClass('boring')).toBe(true); $location.path('/bar'); $rootScope.$digest(); $animate.flushNext('enter').element; item = $animate.flushNext('leave').element; $animate.flushNext('addClass').element; expect(item.hasClass('boring')).toBe(true); })); }); it('should not double compile when the route changes', function() { module('ngAnimate'); module('mock.animate'); var window; module(function($routeProvider, $animateProvider, $provide) { $routeProvider.when('/foo', {template: '
                      {{i}}
                      '}); $routeProvider.when('/bar', {template: '
                      {{i}}
                      '}); $animateProvider.register('.my-animation', function() { return { leave: function(element, done) { done(); } }; }); }); inject(function($rootScope, $compile, $location, $route, $timeout, $rootElement, $sniffer, $animate) { element = $compile(html('
                      '))($rootScope); $animate.enabled(true); $location.path('/foo'); $rootScope.$digest(); $animate.flushNext('enter'); //ngView $animate.flushNext('enter'); //repeat 1 $animate.flushNext('enter'); //repeat 2 expect(element.text()).toEqual('12'); $location.path('/bar'); $rootScope.$digest(); $animate.flushNext('enter'); //ngView new $animate.flushNext('leave'); //ngView old $rootScope.$digest(); expect(n(element.text())).toEqual(''); //this is midway during the animation $animate.flushNext('enter'); //ngRepeat 3 $animate.flushNext('enter'); //ngRepeat 4 $rootScope.$digest(); expect(element.text()).toEqual('34'); function n(text) { return text.replace(/\r\n/m, '').replace(/\r\n/m, ''); } }); }); describe('autoscroll', function () { var autoScrollSpy; function spyOnAnchorScroll() { return function($provide, $routeProvider) { autoScrollSpy = jasmine.createSpy('$anchorScroll'); $provide.value('$anchorScroll', autoScrollSpy); $routeProvider.when('/foo', { controller: angular.noop, template: '
                      ' }); }; } function spyOnAnimateEnter() { return function($animate) { spyOn($animate, 'enter').andCallThrough(); }; } function compileAndLink(tpl) { return function($compile, $rootScope, $location) { element = $compile(tpl)($rootScope); }; } beforeEach(module(spyOnAnchorScroll(), 'mock.animate')); beforeEach(inject(spyOnAnimateEnter())); it('should call $anchorScroll if autoscroll attribute is present', inject( compileAndLink('
                      '), function($rootScope, $animate, $timeout, $location) { $location.path('/foo'); $rootScope.$digest(); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); it('should call $anchorScroll if autoscroll evaluates to true', inject( compileAndLink('
                      '), function($rootScope, $animate, $timeout, $location) { $rootScope.value = true; $location.path('/foo'); $rootScope.$digest(); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); it('should not call $anchorScroll if autoscroll attribute is not present', inject( compileAndLink('
                      '), function($rootScope, $location, $animate, $timeout) { $location.path('/foo'); $rootScope.$digest(); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); it('should not call $anchorScroll if autoscroll evaluates to false', inject( compileAndLink('
                      '), function($rootScope, $location, $animate, $timeout) { $rootScope.value = false; $location.path('/foo'); $rootScope.$digest(); $animate.flushNext('enter'); $timeout.flush(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); it('should only call $anchorScroll after the "enter" animation completes', inject( compileAndLink('
                      '), function($rootScope, $location, $animate, $timeout) { $location.path('/foo'); expect($animate.enter).not.toHaveBeenCalled(); $rootScope.$digest(); expect(autoScrollSpy).not.toHaveBeenCalled(); $animate.flushNext('enter'); $timeout.flush(); expect($animate.enter).toHaveBeenCalledOnce(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); }); }); angular.js-1.2.11/test/ngRoute/routeParamsSpec.js000066400000000000000000000046051227375216300217320ustar00rootroot00000000000000'use strict'; describe('$routeParams', function() { beforeEach(module('ngRoute')); it('should publish the params into a service', function() { module(function($routeProvider) { $routeProvider.when('/foo', {}); $routeProvider.when('/bar/:barId', {}); }); inject(function($rootScope, $route, $location, $routeParams) { $location.path('/foo').search('a=b'); $rootScope.$digest(); expect($routeParams).toEqual({a:'b'}); $location.path('/bar/123').search('x=abc'); $rootScope.$digest(); expect($routeParams).toEqual({barId:'123', x:'abc'}); }); }); it('should correctly extract the params when a param name is part of the route', function() { module(function($routeProvider) { $routeProvider.when('/bar/:foo/:bar', {}); }); inject(function($rootScope, $route, $location, $routeParams) { $location.path('/bar/foovalue/barvalue'); $rootScope.$digest(); expect($routeParams).toEqual({bar:'barvalue', foo:'foovalue'}); }); }); it('should support route params not preceded by slashes', function() { module(function($routeProvider) { $routeProvider.when('/bar:barId/foo:fooId/', {}); }); inject(function($rootScope, $route, $location, $routeParams) { $location.path('/barbarvalue/foofoovalue/'); $rootScope.$digest(); expect($routeParams).toEqual({barId: 'barvalue', fooId: 'foovalue'}); }); }); it('should correctly extract the params when an optional param name is part of the route', function() { module(function($routeProvider) { $routeProvider.when('/bar/:foo?', {}); $routeProvider.when('/baz/:foo?/edit', {}); $routeProvider.when('/qux/:bar?/:baz?', {}); }); inject(function($rootScope, $route, $location, $routeParams) { $location.path('/bar'); $rootScope.$digest(); expect($routeParams).toEqual({}); $location.path('/bar/fooValue'); $rootScope.$digest(); expect($routeParams).toEqual({foo: 'fooValue'}); $location.path('/baz/fooValue/edit'); $rootScope.$digest(); expect($routeParams).toEqual({foo: 'fooValue'}); $location.path('/baz/edit'); $rootScope.$digest(); expect($routeParams).toEqual({}); $location.path('/qux//bazValue'); $rootScope.$digest(); expect($routeParams).toEqual({baz: 'bazValue', bar: undefined}); }); }); }); angular.js-1.2.11/test/ngRoute/routeSpec.js000066400000000000000000001112471227375216300205670ustar00rootroot00000000000000'use strict'; describe('$route', function() { var $httpBackend; beforeEach(module('ngRoute')); beforeEach(module(function() { return function(_$httpBackend_) { $httpBackend = _$httpBackend_; $httpBackend.when('GET', 'Chapter.html').respond('chapter'); $httpBackend.when('GET', 'test.html').respond('test'); $httpBackend.when('GET', 'foo.html').respond('foo'); $httpBackend.when('GET', 'baz.html').respond('baz'); $httpBackend.when('GET', 'bar.html').respond('bar'); $httpBackend.when('GET', 'http://example.com/trusted-template.html').respond('cross domain trusted template'); $httpBackend.when('GET', '404.html').respond('not found'); }; })); it('should route and fire change event', function() { var log = '', lastRoute, nextRoute; module(function($routeProvider) { $routeProvider.when('/Book/:book/Chapter/:chapter', {controller: angular.noop, templateUrl: 'Chapter.html'}); $routeProvider.when('/Blank', {}); }); inject(function($route, $location, $rootScope) { $rootScope.$on('$routeChangeStart', function(event, next, current) { log += 'before();'; expect(current).toBe($route.current); lastRoute = current; nextRoute = next; }); $rootScope.$on('$routeChangeSuccess', function(event, current, last) { log += 'after();'; expect(current).toBe($route.current); expect(lastRoute).toBe(last); expect(nextRoute).toBe(current); }); $location.path('/Book/Moby/Chapter/Intro').search('p=123'); $rootScope.$digest(); $httpBackend.flush(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'}); log = ''; $location.path('/Blank').search('ignore'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({ignore:true}); log = ''; $location.path('/NONE'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current).toEqual(null); }); }); it('should route and fire change event when catch-all params are used', function() { var log = '', lastRoute, nextRoute; module(function($routeProvider) { $routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit', {controller: angular.noop, templateUrl: 'Chapter.html'}); $routeProvider.when('/Book2/:book/:highlight*/Chapter/:chapter', {controller: angular.noop, templateUrl: 'Chapter.html'}); $routeProvider.when('/Blank', {}); }); inject(function($route, $location, $rootScope) { $rootScope.$on('$routeChangeStart', function(event, next, current) { log += 'before();'; expect(current).toBe($route.current); lastRoute = current; nextRoute = next; }); $rootScope.$on('$routeChangeSuccess', function(event, current, last) { log += 'after();'; expect(current).toBe($route.current); expect(lastRoute).toBe(last); expect(nextRoute).toBe(current); }); $location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123'); $rootScope.$digest(); $httpBackend.flush(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'}); log = ''; $location.path('/Blank').search('ignore'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({ignore:true}); log = ''; $location.path('/Book1/Moby/Chapter/Intro/one/two/edit').search('p=123'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'}); log = ''; $location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'}); log = ''; $location.path('/NONE'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current).toEqual(null); }); }); it('should route and fire change event correctly whenever the case insensitive flag is utilized', function() { var log = '', lastRoute, nextRoute; module(function($routeProvider) { $routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit', {controller: angular.noop, templateUrl: 'Chapter.html', caseInsensitiveMatch: true}); $routeProvider.when('/Book2/:book/:highlight*/Chapter/:chapter', {controller: angular.noop, templateUrl: 'Chapter.html'}); $routeProvider.when('/Blank', {}); }); inject(function($route, $location, $rootScope) { $rootScope.$on('$routeChangeStart', function(event, next, current) { log += 'before();'; expect(current).toBe($route.current); lastRoute = current; nextRoute = next; }); $rootScope.$on('$routeChangeSuccess', function(event, current, last) { log += 'after();'; expect(current).toBe($route.current); expect(lastRoute).toBe(last); expect(nextRoute).toBe(current); }); $location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123'); $rootScope.$digest(); $httpBackend.flush(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'}); log = ''; $location.path('/BOOK1/Moby/CHAPTER/Intro/one/EDIT').search('p=123'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'}); log = ''; $location.path('/Blank').search('ignore'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({ignore:true}); log = ''; $location.path('/BLANK'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current).toEqual(null); log = ''; $location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'}); log = ''; $location.path('/BOOK2/Moby/one/two/CHAPTER/Intro').search('p=123'); $rootScope.$digest(); expect(log).toEqual('before();after();'); expect($route.current).toEqual(null); }); }); it('should not change route when location is canceled', function() { module(function($routeProvider) { $routeProvider.when('/somePath', {template: 'some path'}); }); inject(function($route, $location, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event) { $log.info('$locationChangeStart'); event.preventDefault(); }); $rootScope.$on('$beforeRouteChange', function(event) { throw new Error('Should not get here'); }); $location.path('/somePath'); $rootScope.$digest(); expect($log.info.logs.shift()).toEqual(['$locationChangeStart']); }); }); describe('should match a route that contains special chars in the path', function() { beforeEach(module(function($routeProvider) { $routeProvider.when('/$test.23/foo*(bar)/:baz', {templateUrl: 'test.html'}); })); it('matches the full path', inject(function($route, $location, $rootScope) { $location.path('/test'); $rootScope.$digest(); expect($route.current).toBeUndefined(); })); it('matches literal .', inject(function($route, $location, $rootScope) { $location.path('/$testX23/foo*(bar)/222'); $rootScope.$digest(); expect($route.current).toBeUndefined(); })); it('matches literal *', inject(function($route, $location, $rootScope) { $location.path('/$test.23/foooo(bar)/222'); $rootScope.$digest(); expect($route.current).toBeUndefined(); })); it('treats backslashes normally', inject(function($route, $location, $rootScope) { $location.path('/$test.23/foo*\\(bar)/222'); $rootScope.$digest(); expect($route.current).toBeUndefined(); })); it('matches a URL with special chars', inject(function($route, $location, $rootScope) { $location.path('/$test.23/foo*(bar)/222'); $rootScope.$digest(); expect($route.current).toBeDefined(); })); }); describe('should match a route that contains optional params in the path', function() { beforeEach(module(function($routeProvider) { $routeProvider.when('/test/:opt?/:baz/edit', {templateUrl: 'test.html'}); })); it('matches a URL with optional params', inject(function($route, $location, $rootScope) { $location.path('/test/optValue/bazValue/edit'); $rootScope.$digest(); expect($route.current).toBeDefined(); })); it('matches a URL without optional param', inject(function($route, $location, $rootScope) { $location.path('/test//bazValue/edit'); $rootScope.$digest(); expect($route.current).toBeDefined(); })); it('not match a URL with a required param', inject(function($route, $location, $rootScope) { $location.path('///edit'); $rootScope.$digest(); expect($route.current).not.toBeDefined(); })); }); it('should change route even when only search param changes', function() { module(function($routeProvider) { $routeProvider.when('/test', {templateUrl: 'test.html'}); }); inject(function($route, $location, $rootScope) { var callback = jasmine.createSpy('onRouteChange'); $rootScope.$on('$routeChangeStart', callback); $location.path('/test'); $rootScope.$digest(); callback.reset(); $location.search({any: true}); $rootScope.$digest(); expect(callback).toHaveBeenCalled(); }); }); it('should allow routes to be defined with just templates without controllers', function() { module(function($routeProvider) { $routeProvider.when('/foo', {templateUrl: 'foo.html'}); }); inject(function($route, $location, $rootScope) { var onChangeSpy = jasmine.createSpy('onChange'); $rootScope.$on('$routeChangeStart', onChangeSpy); expect($route.current).toBeUndefined(); expect(onChangeSpy).not.toHaveBeenCalled(); $location.path('/foo'); $rootScope.$digest(); expect($route.current.templateUrl).toEqual('foo.html'); expect($route.current.controller).toBeUndefined(); expect(onChangeSpy).toHaveBeenCalled(); }); }); it('should chain whens and otherwise', function() { module(function($routeProvider){ $routeProvider.when('/foo', {templateUrl: 'foo.html'}). otherwise({templateUrl: 'bar.html'}). when('/baz', {templateUrl: 'baz.html'}); }); inject(function($route, $location, $rootScope) { $rootScope.$digest(); expect($route.current.templateUrl).toBe('bar.html'); $location.url('/baz'); $rootScope.$digest(); expect($route.current.templateUrl).toBe('baz.html'); }); }); it('should skip routes with incomplete params', function() { module(function($routeProvider) { $routeProvider .otherwise({template: 'other'}) .when('/pages/:page/:comment*', {template: 'comment'}) .when('/pages/:page', {template: 'page'}) .when('/pages', {template: 'index'}) .when('/foo/', {template: 'foo'}) .when('/foo/:bar', {template: 'bar'}) .when('/foo/:bar*/:baz', {template: 'baz'}); }); inject(function($route, $location, $rootScope) { $location.url('/pages/'); $rootScope.$digest(); expect($route.current.template).toBe('index'); $location.url('/pages/page/'); $rootScope.$digest(); expect($route.current.template).toBe('page'); $location.url('/pages/page/1/'); $rootScope.$digest(); expect($route.current.template).toBe('comment'); $location.url('/foo/'); $rootScope.$digest(); expect($route.current.template).toBe('foo'); $location.url('/foo/bar/'); $rootScope.$digest(); expect($route.current.template).toBe('bar'); $location.url('/foo/bar/baz/'); $rootScope.$digest(); expect($route.current.template).toBe('baz'); $location.url('/something/'); $rootScope.$digest(); expect($route.current.template).toBe('other'); }); }); describe('otherwise', function() { it('should handle unknown routes with "otherwise" route definition', function() { function NotFoundCtrl() {} module(function($routeProvider){ $routeProvider.when('/foo', {templateUrl: 'foo.html'}); $routeProvider.otherwise({templateUrl: '404.html', controller: NotFoundCtrl}); }); inject(function($route, $location, $rootScope) { var onChangeSpy = jasmine.createSpy('onChange'); $rootScope.$on('$routeChangeStart', onChangeSpy); expect($route.current).toBeUndefined(); expect(onChangeSpy).not.toHaveBeenCalled(); $location.path('/unknownRoute'); $rootScope.$digest(); expect($route.current.templateUrl).toBe('404.html'); expect($route.current.controller).toBe(NotFoundCtrl); expect(onChangeSpy).toHaveBeenCalled(); onChangeSpy.reset(); $location.path('/foo'); $rootScope.$digest(); expect($route.current.templateUrl).toEqual('foo.html'); expect($route.current.controller).toBeUndefined(); expect(onChangeSpy).toHaveBeenCalled(); }); }); it('should update $route.current and $route.next when default route is matched', function() { module(function($routeProvider){ $routeProvider.when('/foo', {templateUrl: 'foo.html'}); $routeProvider.otherwise({templateUrl: '404.html'}); }); inject(function($route, $location, $rootScope) { var currentRoute, nextRoute, onChangeSpy = jasmine.createSpy('onChange').andCallFake(function(e, next) { currentRoute = $route.current; nextRoute = next; }); // init $rootScope.$on('$routeChangeStart', onChangeSpy); expect($route.current).toBeUndefined(); expect(onChangeSpy).not.toHaveBeenCalled(); // match otherwise route $location.path('/unknownRoute'); $rootScope.$digest(); expect(currentRoute).toBeUndefined(); expect(nextRoute.templateUrl).toBe('404.html'); expect($route.current.templateUrl).toBe('404.html'); expect(onChangeSpy).toHaveBeenCalled(); onChangeSpy.reset(); // match regular route $location.path('/foo'); $rootScope.$digest(); expect(currentRoute.templateUrl).toBe('404.html'); expect(nextRoute.templateUrl).toBe('foo.html'); expect($route.current.templateUrl).toEqual('foo.html'); expect(onChangeSpy).toHaveBeenCalled(); onChangeSpy.reset(); // match otherwise route again $location.path('/anotherUnknownRoute'); $rootScope.$digest(); expect(currentRoute.templateUrl).toBe('foo.html'); expect(nextRoute.templateUrl).toBe('404.html'); expect($route.current.templateUrl).toEqual('404.html'); expect(onChangeSpy).toHaveBeenCalled(); }); }); }); describe('events', function() { it('should not fire $after/beforeRouteChange during bootstrap (if no route)', function() { var routeChangeSpy = jasmine.createSpy('route change'); module(function($routeProvider) { $routeProvider.when('/one', {}); // no otherwise defined }); inject(function($rootScope, $route, $location) { $rootScope.$on('$routeChangeStart', routeChangeSpy); $rootScope.$on('$routeChangeSuccess', routeChangeSpy); $rootScope.$digest(); expect(routeChangeSpy).not.toHaveBeenCalled(); $location.path('/no-route-here'); $rootScope.$digest(); expect(routeChangeSpy).not.toHaveBeenCalled(); }); }); it('should fire $routeChangeStart and resolve promises', function() { var deferA, deferB; module(function($provide, $routeProvider) { $provide.factory('b', function($q) { deferB = $q.defer(); return deferB.promise; }); $routeProvider.when('/path', { templateUrl: 'foo.html', resolve: { a: ['$q', function($q) { deferA = $q.defer(); return deferA.promise; }], b: 'b' } }); }); inject(function($location, $route, $rootScope, $httpBackend) { var log = ''; $httpBackend.expectGET('foo.html').respond('FOO'); $location.path('/path'); $rootScope.$digest(); expect(log).toEqual(''); $httpBackend.flush(); expect(log).toEqual(''); deferA.resolve(); $rootScope.$digest(); expect(log).toEqual(''); deferB.resolve(); $rootScope.$digest(); expect($route.current.locals.$template).toEqual('FOO'); }); }); it('should fire $routeChangeError event on resolution error', function() { var deferA; module(function($provide, $routeProvider) { $routeProvider.when('/path', { template: 'foo', resolve: { a: function($q) { deferA = $q.defer(); return deferA.promise; } } }); }); inject(function($location, $route, $rootScope) { var log = ''; $rootScope.$on('$routeChangeStart', function() { log += 'before();'; }); $rootScope.$on('$routeChangeError', function(e, n, l, reason) { log += 'failed(' + reason + ');'; }); $location.path('/path'); $rootScope.$digest(); expect(log).toEqual('before();'); deferA.reject('MyError'); $rootScope.$digest(); expect(log).toEqual('before();failed(MyError);'); }); }); it('should fetch templates', function() { module(function($routeProvider) { $routeProvider. when('/r1', { templateUrl: 'r1.html' }). when('/r2', { templateUrl: 'r2.html' }); }); inject(function($route, $httpBackend, $location, $rootScope) { var log = ''; $rootScope.$on('$routeChangeStart', function(e, next) { log += '$before(' + next.templateUrl + ');'}); $rootScope.$on('$routeChangeSuccess', function(e, next) { log += '$after(' + next.templateUrl + ');'}); $httpBackend.expectGET('r1.html').respond('R1'); $httpBackend.expectGET('r2.html').respond('R2'); $location.path('/r1'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);'); $location.path('/r2'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);$before(r2.html);'); $httpBackend.flush(); expect(log).toBe('$before(r1.html);$before(r2.html);$after(r2.html);'); expect(log).not.toContain('$after(r1.html);'); }); }); it('should NOT load cross domain templates by default', function() { module(function($routeProvider) { $routeProvider.when('/foo', { templateUrl: 'http://example.com/foo.html' }); }); inject(function ($route, $location, $rootScope) { $location.path('/foo'); expect(function() { $rootScope.$digest(); }).toThrowMinErr('$sce', 'insecurl', 'Blocked loading resource from url not allowed by ' + '$sceDelegate policy. URL: http://example.com/foo.html'); }); }); it('should load cross domain templates that are trusted', function() { module(function($routeProvider, $sceDelegateProvider) { $routeProvider.when('/foo', { templateUrl: 'http://example.com/foo.html' }); $sceDelegateProvider.resourceUrlWhitelist([/^http:\/\/example\.com\/foo\.html$/]); }); inject(function ($route, $location, $rootScope) { $httpBackend.whenGET('http://example.com/foo.html').respond('FOO BODY'); $location.path('/foo'); $rootScope.$digest(); $httpBackend.flush(); expect($route.current.locals.$template).toEqual('FOO BODY'); }); }); it('should not update $routeParams until $routeChangeSuccess', function() { module(function($routeProvider) { $routeProvider. when('/r1/:id', { templateUrl: 'r1.html' }). when('/r2/:id', { templateUrl: 'r2.html' }); }); inject(function($route, $httpBackend, $location, $rootScope, $routeParams) { var log = ''; $rootScope.$on('$routeChangeStart', function(e, next) { log += '$before' + angular.toJson($routeParams) + ';'}); $rootScope.$on('$routeChangeSuccess', function(e, next) { log += '$after' + angular.toJson($routeParams) + ';'}); $httpBackend.whenGET('r1.html').respond('R1'); $httpBackend.whenGET('r2.html').respond('R2'); $location.path('/r1/1'); $rootScope.$digest(); expect(log).toBe('$before{};'); $httpBackend.flush(); expect(log).toBe('$before{};$after{"id":"1"};'); log = ''; $location.path('/r2/2'); $rootScope.$digest(); expect(log).toBe('$before{"id":"1"};'); $httpBackend.flush(); expect(log).toBe('$before{"id":"1"};$after{"id":"2"};'); }); }); it('should drop in progress route change when new route change occurs', function() { module(function($routeProvider) { $routeProvider. when('/r1', { templateUrl: 'r1.html' }). when('/r2', { templateUrl: 'r2.html' }); }); inject(function($route, $httpBackend, $location, $rootScope) { var log = ''; $rootScope.$on('$routeChangeStart', function(e, next) { log += '$before(' + next.templateUrl + ');'}); $rootScope.$on('$routeChangeSuccess', function(e, next) { log += '$after(' + next.templateUrl + ');'}); $httpBackend.expectGET('r1.html').respond('R1'); $httpBackend.expectGET('r2.html').respond('R2'); $location.path('/r1'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);'); $location.path('/r2'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);$before(r2.html);'); $httpBackend.flush(); expect(log).toBe('$before(r1.html);$before(r2.html);$after(r2.html);'); expect(log).not.toContain('$after(r1.html);'); }); }); it('should drop in progress route change when new route change occurs and old fails', function() { module(function($routeProvider) { $routeProvider. when('/r1', { templateUrl: 'r1.html' }). when('/r2', { templateUrl: 'r2.html' }); }); inject(function($route, $httpBackend, $location, $rootScope) { var log = ''; $rootScope.$on('$routeChangeError', function(e, next, last, error) { log += '$failed(' + next.templateUrl + ', ' + error.status + ');'; }); $rootScope.$on('$routeChangeStart', function(e, next) { log += '$before(' + next.templateUrl + ');'}); $rootScope.$on('$routeChangeSuccess', function(e, next) { log += '$after(' + next.templateUrl + ');'}); $httpBackend.expectGET('r1.html').respond(404, 'R1'); $httpBackend.expectGET('r2.html').respond('R2'); $location.path('/r1'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);'); $location.path('/r2'); $rootScope.$digest(); expect(log).toBe('$before(r1.html);$before(r2.html);'); $httpBackend.flush(); expect(log).toBe('$before(r1.html);$before(r2.html);$after(r2.html);'); expect(log).not.toContain('$after(r1.html);'); }); }); it('should catch local factory errors', function() { var myError = new Error('MyError'); module(function($routeProvider, $exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); $routeProvider.when('/locals', { resolve: { a: function($q) { throw myError; } } }); }); inject(function($location, $route, $rootScope, $exceptionHandler) { $location.path('/locals'); $rootScope.$digest(); expect($exceptionHandler.errors).toEqual([myError]); }); }); }); it('should match route with and without trailing slash', function() { module(function($routeProvider){ $routeProvider.when('/foo', {templateUrl: 'foo.html'}); $routeProvider.when('/bar/', {templateUrl: 'bar.html'}); }); inject(function($route, $location, $rootScope) { $location.path('/foo'); $rootScope.$digest(); expect($location.path()).toBe('/foo'); expect($route.current.templateUrl).toBe('foo.html'); $location.path('/foo/'); $rootScope.$digest(); expect($location.path()).toBe('/foo'); expect($route.current.templateUrl).toBe('foo.html'); $location.path('/bar'); $rootScope.$digest(); expect($location.path()).toBe('/bar/'); expect($route.current.templateUrl).toBe('bar.html'); $location.path('/bar/'); $rootScope.$digest(); expect($location.path()).toBe('/bar/'); expect($route.current.templateUrl).toBe('bar.html'); }); }); describe('redirection', function() { it('should support redirection via redirectTo property by updating $location', function() { module(function($routeProvider) { $routeProvider.when('/', {redirectTo: '/foo'}); $routeProvider.when('/foo', {templateUrl: 'foo.html'}); $routeProvider.when('/bar', {templateUrl: 'bar.html'}); $routeProvider.when('/baz', {redirectTo: '/bar'}); $routeProvider.otherwise({templateUrl: '404.html'}); }); inject(function($route, $location, $rootScope) { var onChangeSpy = jasmine.createSpy('onChange'); $rootScope.$on('$routeChangeStart', onChangeSpy); expect($route.current).toBeUndefined(); expect(onChangeSpy).not.toHaveBeenCalled(); $location.path('/'); $rootScope.$digest(); expect($location.path()).toBe('/foo'); expect($route.current.templateUrl).toBe('foo.html'); expect(onChangeSpy.callCount).toBe(2); onChangeSpy.reset(); $location.path('/baz'); $rootScope.$digest(); expect($location.path()).toBe('/bar'); expect($route.current.templateUrl).toBe('bar.html'); expect(onChangeSpy.callCount).toBe(2); }); }); it('should interpolate route vars in the redirected path from original path', function() { module(function($routeProvider) { $routeProvider.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'}); $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/baz/:id/:path*', {redirectTo: '/path/:path/:id'}); $routeProvider.when('/path/:path*/:id', {templateUrl: 'foo.html'}); }); inject(function($route, $location, $rootScope) { $location.path('/foo/id1/foo/subid3/gah'); $rootScope.$digest(); expect($location.path()).toEqual('/bar/id1/subid3/23'); expect($location.search()).toEqual({extraId: 'gah'}); expect($route.current.templateUrl).toEqual('bar.html'); $location.path('/baz/1/foovalue/barvalue'); $rootScope.$digest(); expect($location.path()).toEqual('/path/foovalue/barvalue/1'); expect($route.current.templateUrl).toEqual('foo.html'); }); }); it('should interpolate route vars in the redirected path from original search', function() { module(function($routeProvider) { $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'}); }); inject(function($route, $location, $rootScope) { $location.path('/foo/id3/eId').search('subid=sid1&appended=true'); $rootScope.$digest(); expect($location.path()).toEqual('/bar/id3/sid1/99'); expect($location.search()).toEqual({appended: 'true', extra: 'eId'}); expect($route.current.templateUrl).toEqual('bar.html'); }); }); it('should allow custom redirectTo function to be used', function() { function customRedirectFn(routePathParams, path, search) { expect(routePathParams).toEqual({id: 'id3'}); expect(path).toEqual('/foo/id3'); expect(search).toEqual({ subid: 'sid1', appended: 'true' }); return '/custom'; } module(function($routeProvider){ $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id', {redirectTo: customRedirectFn}); }); inject(function($route, $location, $rootScope) { $location.path('/foo/id3').search('subid=sid1&appended=true'); $rootScope.$digest(); expect($location.path()).toEqual('/custom'); }); }); it('should replace the url when redirecting', function() { module(function($routeProvider) { $routeProvider.when('/bar/:id', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id/:extra', {redirectTo: '/bar/:id'}); }); inject(function($browser, $route, $location, $rootScope) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough(); $location.path('/foo/id3/eId'); $rootScope.$digest(); expect($location.path()).toEqual('/bar/id3'); expect($browserUrl.mostRecentCall.args) .toEqual(['http://server/#/bar/id3?extra=eId', true]); }); }); }); describe('reloadOnSearch', function() { it('should reload a route when reloadOnSearch is enabled and .search() changes', function() { var reloaded = jasmine.createSpy('route reload'); module(function($routeProvider) { $routeProvider.when('/foo', {controller: angular.noop}); }); inject(function($route, $location, $rootScope, $routeParams) { $rootScope.$on('$routeChangeStart', reloaded); $location.path('/foo'); $rootScope.$digest(); expect(reloaded).toHaveBeenCalled(); expect($routeParams).toEqual({}); reloaded.reset(); // trigger reload $location.search({foo: 'bar'}); $rootScope.$digest(); expect(reloaded).toHaveBeenCalled(); expect($routeParams).toEqual({foo:'bar'}); }); }); it('should not reload a route when reloadOnSearch is disabled and only .search() changes', function() { var routeChange = jasmine.createSpy('route change'), routeUpdate = jasmine.createSpy('route update'); module(function($routeProvider) { $routeProvider.when('/foo', {controller: angular.noop, reloadOnSearch: false}); }); inject(function($route, $location, $rootScope) { $rootScope.$on('$routeChangeStart', routeChange); $rootScope.$on('$routeChangeSuccess', routeChange); $rootScope.$on('$routeUpdate', routeUpdate); expect(routeChange).not.toHaveBeenCalled(); $location.path('/foo'); $rootScope.$digest(); expect(routeChange).toHaveBeenCalled(); expect(routeChange.callCount).toBe(2); expect(routeUpdate).not.toHaveBeenCalled(); routeChange.reset(); // don't trigger reload $location.search({foo: 'bar'}); $rootScope.$digest(); expect(routeChange).not.toHaveBeenCalled(); expect(routeUpdate).toHaveBeenCalled(); }); }); it('should reload reloadOnSearch route when url differs only in route path param', function() { var routeChange = jasmine.createSpy('route change'); module(function($routeProvider) { $routeProvider.when('/foo/:fooId', {controller: angular.noop, reloadOnSearch: false}); }); inject(function($route, $location, $rootScope) { $rootScope.$on('$routeChangeStart', routeChange); $rootScope.$on('$routeChangeSuccess', routeChange); expect(routeChange).not.toHaveBeenCalled(); $location.path('/foo/aaa'); $rootScope.$digest(); expect(routeChange).toHaveBeenCalled(); expect(routeChange.callCount).toBe(2); routeChange.reset(); $location.path('/foo/bbb'); $rootScope.$digest(); expect(routeChange).toHaveBeenCalled(); expect(routeChange.callCount).toBe(2); routeChange.reset(); $location.search({foo: 'bar'}); $rootScope.$digest(); expect(routeChange).not.toHaveBeenCalled(); }); }); it('should update params when reloadOnSearch is disabled and .search() changes', function() { var routeParamsWatcher = jasmine.createSpy('routeParamsWatcher'); module(function($routeProvider) { $routeProvider.when('/foo', {controller: angular.noop}); $routeProvider.when('/bar/:barId', {controller: angular.noop, reloadOnSearch: false}); }); inject(function($route, $location, $rootScope, $routeParams) { $rootScope.$watch(function() { return $routeParams; }, function(value) { routeParamsWatcher(value); }, true); expect(routeParamsWatcher).not.toHaveBeenCalled(); $location.path('/foo'); $rootScope.$digest(); expect(routeParamsWatcher).toHaveBeenCalledWith({}); routeParamsWatcher.reset(); // trigger reload $location.search({foo: 'bar'}); $rootScope.$digest(); expect(routeParamsWatcher).toHaveBeenCalledWith({foo: 'bar'}); routeParamsWatcher.reset(); $location.path('/bar/123').search({}); $rootScope.$digest(); expect(routeParamsWatcher).toHaveBeenCalledWith({barId: '123'}); routeParamsWatcher.reset(); // don't trigger reload $location.search({foo: 'bar'}); $rootScope.$digest(); expect(routeParamsWatcher).toHaveBeenCalledWith({barId: '123', foo: 'bar'}); }); }); it('should allow using a function as a template', function() { var customTemplateWatcher = jasmine.createSpy('customTemplateWatcher'); function customTemplateFn(routePathParams) { customTemplateWatcher(routePathParams); expect(routePathParams).toEqual({id: 'id3'}); return '

                      ' + routePathParams.id + '

                      '; } module(function($routeProvider) { $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id', {template: customTemplateFn}); }); inject(function($route, $location, $rootScope) { $location.path('/foo/id3'); $rootScope.$digest(); expect(customTemplateWatcher).toHaveBeenCalledWith({id: 'id3'}); }); }); it('should allow using a function as a templateUrl', function() { var customTemplateUrlWatcher = jasmine.createSpy('customTemplateUrlWatcher'); function customTemplateUrlFn(routePathParams) { customTemplateUrlWatcher(routePathParams); expect(routePathParams).toEqual({id: 'id3'}); return 'foo.html'; } module(function($routeProvider){ $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id', {templateUrl: customTemplateUrlFn}); }); inject(function($route, $location, $rootScope) { $location.path('/foo/id3'); $rootScope.$digest(); expect(customTemplateUrlWatcher).toHaveBeenCalledWith({id: 'id3'}); expect($route.current.loadedTemplateUrl).toEqual('foo.html'); }); }); describe('reload', function() { it('should reload even if reloadOnSearch is false', function() { var routeChangeSpy = jasmine.createSpy('route change'); module(function($routeProvider) { $routeProvider.when('/bar/:barId', {controller: angular.noop, reloadOnSearch: false}); }); inject(function($route, $location, $rootScope, $routeParams) { $rootScope.$on('$routeChangeSuccess', routeChangeSpy); $location.path('/bar/123'); $rootScope.$digest(); expect($routeParams).toEqual({barId:'123'}); expect(routeChangeSpy).toHaveBeenCalledOnce(); routeChangeSpy.reset(); $location.path('/bar/123').search('a=b'); $rootScope.$digest(); expect($routeParams).toEqual({barId:'123', a:'b'}); expect(routeChangeSpy).not.toHaveBeenCalled(); $route.reload(); $rootScope.$digest(); expect($routeParams).toEqual({barId:'123', a:'b'}); expect(routeChangeSpy).toHaveBeenCalledOnce(); }); }); }); }); }); angular.js-1.2.11/test/ngSanitize/000077500000000000000000000000001227375216300167425ustar00rootroot00000000000000angular.js-1.2.11/test/ngSanitize/directive/000077500000000000000000000000001227375216300207205ustar00rootroot00000000000000angular.js-1.2.11/test/ngSanitize/directive/ngBindHtmlSpec.js000066400000000000000000000015741227375216300241260ustar00rootroot00000000000000'use strict'; describe('ngBindHtml', function() { beforeEach(module('ngSanitize')); it('should set html', inject(function($rootScope, $compile) { var element = $compile('
                      ')($rootScope); $rootScope.html = '
                      hello
                      '; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('
                      hello
                      '); })); it('should reset html when value is null or undefined', inject(function($compile, $rootScope) { var element = $compile('
                      ')($rootScope); angular.forEach([null, undefined, ''], function(val) { $rootScope.html = 'some val'; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual('some val'); $rootScope.html = val; $rootScope.$digest(); expect(angular.lowercase(element.html())).toEqual(''); }); })); }); angular.js-1.2.11/test/ngSanitize/filter/000077500000000000000000000000001227375216300202275ustar00rootroot00000000000000angular.js-1.2.11/test/ngSanitize/filter/linkySpec.js000066400000000000000000000025251227375216300225320ustar00rootroot00000000000000describe('linky', function() { var linky; beforeEach(module('ngSanitize')); beforeEach(inject(function($filter){ linky = $filter('linky'); })); it('should do basic filter', function() { expect(linky("http://ab/ (http://a/) http://1.2/v:~-123. c")). toEqual('http://ab/ ' + '(http://a/) ' + '<http://a/> ' + 'http://1.2/v:~-123. c'); expect(linky(undefined)).not.toBeDefined(); }); it('should handle mailto:', function() { expect(linky("mailto:me@example.com")). toEqual('me@example.com'); expect(linky("me@example.com")). toEqual('me@example.com'); expect(linky("send email to me@example.com, but")). toEqual('send email to me@example.com, but'); }); it('should handle target:', function() { expect(linky("http://example.com", "_blank")). toEqual('http://example.com') expect(linky("http://example.com", "someNamedIFrame")). toEqual('http://example.com') }); }); angular.js-1.2.11/test/ngSanitize/sanitizeSpec.js000066400000000000000000000356741227375216300217600ustar00rootroot00000000000000'use strict'; describe('HTML', function() { var expectHTML; beforeEach(module('ngSanitize')); beforeEach(function() { expectHTML = function(html){ var sanitize; inject(function($sanitize) { sanitize = $sanitize; }); return expect(sanitize(html)); }; }); describe('htmlParser', function() { if (angular.isUndefined(window.htmlParser)) return; var handler, start, text, comment; beforeEach(function() { handler = { start: function(tag, attrs, unary){ start = { tag: tag, attrs: attrs, unary: unary }; // Since different browsers handle newlines differently we trim // so that it is easier to write tests. angular.forEach(attrs, function(value, key) { attrs[key] = value.replace(/^\s*/, '').replace(/\s*$/, '') }); }, chars: function(text_){ text = text_; }, end:function(tag) { expect(tag).toEqual(start.tag); }, comment:function(comment_) { comment = comment_; } }; }); it('should parse comments', function() { htmlParser('', handler); expect(comment).toEqual('FOOBAR'); }); it('should throw an exception for invalid comments', function() { var caught=false; try { htmlParser('', handler); } catch (ex) { caught = true; // expected an exception due to a bad parse } expect(caught).toBe(true); }); it('double-dashes are not allowed in a comment', function() { var caught=false; try { htmlParser('', handler); } catch (ex) { caught = true; // expected an exception due to a bad parse } expect(caught).toBe(true); }); it('should parse basic format', function() { htmlParser('text', handler); expect(start).toEqual({tag:'tag', attrs:{attr:'value'}, unary:false}); expect(text).toEqual('text'); }); it('should parse newlines in tags', function() { htmlParser('<\ntag\n attr="value"\n>text<\n/\ntag\n>', handler); expect(start).toEqual({tag:'tag', attrs:{attr:'value'}, unary:false}); expect(text).toEqual('text'); }); it('should parse newlines in attributes', function() { htmlParser('text', handler); expect(start).toEqual({tag:'tag', attrs:{attr:'value'}, unary:false}); expect(text).toEqual('text'); }); it('should parse namespace', function() { htmlParser('text', handler); expect(start).toEqual({tag:'ns:t-a-g', attrs:{'ns:a-t-t-r':'value'}, unary:false}); expect(text).toEqual('text'); }); it('should parse empty value attribute of node', function() { htmlParser('', handler); expect(start).toEqual({tag:'option', attrs:{selected:'', value:''}, unary:false}); expect(text).toEqual('abc'); }); }); // THESE TESTS ARE EXECUTED WITH COMPILED ANGULAR it('should echo html', function() { expectHTML('helloworld.'). toEqual('helloworld.'); }); it('should remove script', function() { expectHTML('ac.').toEqual('ac.'); }); it('should remove double nested script', function() { expectHTML('ailc.').toEqual('ac.'); }); it('should remove unknown names', function() { expectHTML('abc').toEqual('abc'); }); it('should remove unsafe value', function() { expectHTML('').toEqual(''); }); it('should handle self closed elements', function() { expectHTML('a
                      c').toEqual('a
                      c'); }); it('should handle namespace', function() { expectHTML('abc').toEqual('abc'); }); it('should handle entities', function() { var everything = '
                      ' + '!@#$%^&*()_+-={}[]:";\'<>?,./`~ ħ
                      '; expectHTML(everything).toEqual(everything); }); it('should handle improper html', function() { expectHTML('< div rel="
                      " alt=abc dir=\'"\' >text< /div>'). toEqual('
                      text
                      '); }); it('should handle improper html2', function() { expectHTML('< div rel="
                      " / >'). toEqual('
                      '); }); it('should ignore back slash as escape', function() { expectHTML('xxx\\'). toEqual('xxx\\'); }); it('should ignore object attributes', function() { expectHTML(':)'). toEqual(':)'); expectHTML(':)'). toEqual(''); }); it('should keep spaces as prefix/postfix', function() { expectHTML(' a ').toEqual(' a '); }); it('should allow multiline strings', function() { expectHTML('\na\n').toEqual(' a\ '); }); describe('htmlSanitizerWriter', function() { if (angular.isUndefined(window.htmlSanitizeWriter)) return; var writer, html, uriValidator; beforeEach(function() { html = ''; uriValidator = jasmine.createSpy('uriValidator'); writer = htmlSanitizeWriter({push:function(text){html+=text;}}, uriValidator); }); it('should write basic HTML', function() { writer.chars('before'); writer.start('div', {rel:'123'}, false); writer.chars('in'); writer.end('div'); writer.chars('after'); expect(html).toEqual('before
                      in
                      after'); }); it('should escape text nodes', function() { writer.chars('a
                      &
                      c'); expect(html).toEqual('a<div>&</div>c'); }); it('should escape IE script', function() { writer.chars('&<>{}'); expect(html).toEqual('&<>{}'); }); it('should escape attributes', function() { writer.start('div', {rel:'!@#$%^&*()_+-={}[]:";\'<>?,./`~ \n\0\r\u0127'}); expect(html).toEqual('
                      '); }); it('should ignore missformed elements', function() { writer.start('d>i&v', {}); expect(html).toEqual(''); }); it('should ignore unknown attributes', function() { writer.start('div', {unknown:""}); expect(html).toEqual('
                      '); }); describe('explicitly disallow', function() { it('should not allow attributes', function() { writer.start('div', {id:'a', name:'a', style:'a'}); expect(html).toEqual('
                      '); }); it('should not allow tags', function() { function tag(name) { writer.start(name, {}); writer.end(name); } tag('frameset'); tag('frame'); tag('form'); tag('param'); tag('object'); tag('embed'); tag('textarea'); tag('input'); tag('button'); tag('option'); tag('select'); tag('script'); tag('style'); tag('link'); tag('base'); tag('basefont'); expect(html).toEqual(''); }); }); describe('uri validation', function() { it('should call the uri validator', function() { writer.start('a', {href:'someUrl'}, false); expect(uriValidator).toHaveBeenCalledWith('someUrl', false); uriValidator.reset(); writer.start('img', {src:'someImgUrl'}, false); expect(uriValidator).toHaveBeenCalledWith('someImgUrl', true); uriValidator.reset(); writer.start('someTag', {src:'someNonUrl'}, false); expect(uriValidator).not.toHaveBeenCalled(); }); it('should drop non valid uri attributes', function() { uriValidator.andReturn(false); writer.start('a', {href:'someUrl'}, false); expect(html).toEqual(''); html = ''; uriValidator.andReturn(true); writer.start('a', {href:'someUrl'}, false); expect(html).toEqual(''); }); }); }); describe('uri checking', function() { beforeEach(function() { this.addMatchers({ toBeValidUrl: function() { var sanitize; inject(function($sanitize) { sanitize = $sanitize; }); var input = ''; return sanitize(input) === input; }, toBeValidImageSrc: function() { var sanitize; inject(function($sanitize) { sanitize = $sanitize; }); var input = ''; return sanitize(input) === input; } }); }); it('should use $$sanitizeUri for links', function() { var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); module(function($provide) { $provide.value('$$sanitizeUri', $$sanitizeUri); }); inject(function() { $$sanitizeUri.andReturn('someUri'); expectHTML('').toEqual(''); expect($$sanitizeUri).toHaveBeenCalledWith('someUri', false); $$sanitizeUri.andReturn('unsafe:someUri'); expectHTML('').toEqual(''); }); }); it('should use $$sanitizeUri for links', function() { var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); module(function($provide) { $provide.value('$$sanitizeUri', $$sanitizeUri); }); inject(function() { $$sanitizeUri.andReturn('someUri'); expectHTML('').toEqual(''); expect($$sanitizeUri).toHaveBeenCalledWith('someUri', true); $$sanitizeUri.andReturn('unsafe:someUri'); expectHTML('').toEqual(''); }); }); it('should be URI', function() { expect('').toBeValidUrl(); expect('http://abc').toBeValidUrl(); expect('HTTP://abc').toBeValidUrl(); expect('https://abc').toBeValidUrl(); expect('HTTPS://abc').toBeValidUrl(); expect('ftp://abc').toBeValidUrl(); expect('FTP://abc').toBeValidUrl(); expect('mailto:me@example.com').toBeValidUrl(); expect('MAILTO:me@example.com').toBeValidUrl(); expect('tel:123-123-1234').toBeValidUrl(); expect('TEL:123-123-1234').toBeValidUrl(); expect('#anchor').toBeValidUrl(); expect('/page1.md').toBeValidUrl(); }); it('should not be URI', function() { expect('javascript:alert').not.toBeValidUrl(); }); describe('javascript URLs', function() { it('should ignore javascript:', function() { expect('JavaScript:abc').not.toBeValidUrl(); expect(' \n Java\n Script:abc').not.toBeValidUrl(); expect('http://JavaScript/my.js').toBeValidUrl(); }); it('should ignore dec encoded javascript:', function() { expect('javascript:').not.toBeValidUrl(); expect('javascript:').not.toBeValidUrl(); expect('j avascript:').not.toBeValidUrl(); }); it('should ignore decimal with leading 0 encodede javascript:', function() { expect('javascript:').not.toBeValidUrl(); expect('j avascript:').not.toBeValidUrl(); expect('j avascript:').not.toBeValidUrl(); }); it('should ignore hex encoded javascript:', function() { expect('javascript:').not.toBeValidUrl(); expect('javascript:').not.toBeValidUrl(); expect('j avascript:').not.toBeValidUrl(); }); it('should ignore hex encoded whitespace javascript:', function() { expect('jav ascript:alert();').not.toBeValidUrl(); expect('jav ascript:alert();').not.toBeValidUrl(); expect('jav ascript:alert();').not.toBeValidUrl(); expect('jav\u0000ascript:alert();').not.toBeValidUrl(); expect('java\u0000\u0000script:alert();').not.toBeValidUrl(); expect('  java\u0000\u0000script:alert();').not.toBeValidUrl(); }); }); }); describe('sanitizeText', function() { it('should escape text', function() { expect(sanitizeText('a
                      &
                      c')).toEqual('a<div>&</div>c'); }); }); }); describe('decodeEntities', function() { var handler, text, origHiddenPre = window.hiddenPre; beforeEach(function() { text = ''; handler = { start: function() {}, chars: function(text_){ text = text_; }, end: function() {}, comment: function() {} }; module('ngSanitize'); }); afterEach(function() { window.hiddenPre = origHiddenPre; }); it('should use innerText if textContent is not available (IE<9)', function() { window.hiddenPre = { innerText: 'INNER_TEXT' }; inject(function($sanitize) { htmlParser('text', handler); expect(text).toEqual('INNER_TEXT'); }); }); it('should use textContent if available', function() { window.hiddenPre = { textContent: 'TEXT_CONTENT', innerText: 'INNER_TEXT' }; inject(function($sanitize) { htmlParser('text', handler); expect(text).toEqual('TEXT_CONTENT'); }); }); it('should use textContent even if empty', function() { window.hiddenPre = { textContent: '', innerText: 'INNER_TEXT' }; inject(function($sanitize) { htmlParser('text', handler); expect(text).toEqual(''); }); }); }); angular.js-1.2.11/test/ngScenario/000077500000000000000000000000001227375216300167175ustar00rootroot00000000000000angular.js-1.2.11/test/ngScenario/ApplicationSpec.js000066400000000000000000000103501227375216300223320ustar00rootroot00000000000000'use strict'; describe('angular.scenario.Application', function() { var $window; var app, frames; function callLoadHandlers(app) { var handler = app.getFrame_().triggerHandler('load') } beforeEach(function() { document.body.innerHTML = ''; frames = _jQuery("
                      "); _jQuery(document.body).append(frames); app = new angular.scenario.Application(frames); }); afterEach(function() { _jQuery('iframe').off(); // cleanup any leftover onload handlers document.body.innerHTML = ''; }); it('should return new $window and $document after navigateTo', function() { var called; var testWindow, testDocument, counter = 0; app.getWindow_ = function() { return {x:counter++, document:{x:counter++}}; }; app.navigateTo('http://www.google.com/'); app.executeAction(function($window, $document) { testWindow = $window; testDocument = $document; }); app.navigateTo('http://www.google.com/'); app.executeAction(function($window, $document) { expect($window).not.toEqual(testWindow); expect($document).not.toEqual(testDocument); called = true; }); expect(called).toBeTruthy(); }); it('should execute callback with correct arguments', function() { var called; var testWindow = {document: {}}; app.getWindow_ = function() { return testWindow; }; app.executeAction(function($window, $document) { expect(this).toEqual(app); expect($document).toEqual(_jQuery($window.document)); expect($window).toEqual(testWindow); called = true; }); expect(called).toBeTruthy(); }); it('should use a new iframe each time', function() { app.navigateTo('http://localhost/'); var frame = app.getFrame_(); frame.attr('test', true); app.navigateTo('http://localhost/'); expect(app.getFrame_().attr('test')).toBeFalsy(); }); it('should call error handler if document not accessible', function() { var called; app.getWindow_ = function() { return {}; }; app.navigateTo('http://localhost/', angular.noop, function(error) { expect(error).toMatch(/Sandbox Error/); called = true; }); callLoadHandlers(app); expect(called).toBeTruthy(); }); it('should call error handler if navigating to about:blank', function() { var called; app.navigateTo('about:blank', angular.noop, function(error) { expect(error).toMatch(/Sandbox Error/); called = true; }); expect(called).toBeTruthy(); }); it('should remove old iframes', function() { app.navigateTo('http://localhost/#foo'); frames.find('iframe')[0].id = 'test'; app.navigateTo('http://localhost/#bar'); var iframes = frames.find('iframe'); expect(iframes.length).toEqual(1); expect(iframes[0].src).toEqual('http://localhost/#bar'); expect(iframes[0].id).toBeFalsy(); }); it('should URL update description bar', function() { app.navigateTo('http://localhost/'); var anchor = frames.find('> h2 a'); expect(anchor.attr('href')).toEqual('http://localhost/'); expect(anchor.text()).toEqual('http://localhost/'); }); it('should call onload handler when frame loads', function() { var called; app.getWindow_ = function() { return {document: {}}; }; app.navigateTo('http://localhost/', function($window, $document) { called = true; }); callLoadHandlers(app); expect(called).toBeTruthy(); }); it('should wait for pending requests in executeAction', inject(function($injector, $browser) { var called, polled; var handlers = []; var testWindow = { document: jqLite('
                      ')[0], angular: { element: jqLite, service: {} } }; $browser.notifyWhenNoOutstandingRequests = function(fn) { handlers.push(fn); }; jqLite(testWindow.document).data('$injector', $injector); app.getWindow_ = function() { return testWindow; }; app.executeAction(function($window, $document) { expect($window).toEqual(testWindow); expect($document).toBeDefined(); expect($document[0].className).toEqual('test-foo'); }); expect(handlers.length).toEqual(1); handlers[0](); dealoc(testWindow.document); })); }); angular.js-1.2.11/test/ngScenario/DescribeSpec.js000066400000000000000000000064341227375216300216170ustar00rootroot00000000000000'use strict'; describe('angular.scenario.Describe', function() { var log; var root; beforeEach(function() { root = new angular.scenario.Describe(); /** * Simple callback logging system. Use to assert proper order of calls. */ log = function(text) { log.text = log.text + text; }; log.fn = function(text) { return function(done){ log(text); (done || angular.noop)(); }; }; log.reset = function() { log.text = ''; }; log.reset(); }); it('should handle basic nested case', function() { root.describe('A', function() { this.beforeEach(log.fn('{')); this.afterEach(log.fn('}')); this.it('1', log.fn('1')); this.describe('B', function() { this.beforeEach(log.fn('(')); this.afterEach(log.fn(')')); this.it('2', log.fn('2')); }); }); var specs = root.getSpecs(); expect(specs.length).toEqual(2); expect(specs[0].name).toEqual('2'); specs[0].before(); specs[0].body(); specs[0].after(); expect(log.text).toEqual('{(2)}'); log.reset(); expect(specs[1].name).toEqual('1'); specs[1].before(); specs[1].body(); specs[1].after(); expect(log.text).toEqual('{1}'); }); it('should link nested describe blocks with parent and children', function() { root.describe('A', function() { this.it('1', angular.noop); this.describe('B', function() { this.it('2', angular.noop); this.describe('C', function() { this.it('3', angular.noop); }); }); }); var specs = root.getSpecs(); expect(specs[2].definition.parent).toEqual(root); expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]); }); it('should not process xit and xdescribe', function() { root.describe('A', function() { this.xit('1', angular.noop); this.xdescribe('B', function() { this.it('2', angular.noop); this.describe('C', function() { this.it('3', angular.noop); }); }); }); var specs = root.getSpecs(); expect(specs.length).toEqual(0); }); it('should only return iit and ddescribe if present', function() { root.describe('A', function() { this.it('1', angular.noop); this.iit('2', angular.noop); this.describe('B', function() { this.it('3', angular.noop); this.ddescribe('C', function() { this.it('4', angular.noop); this.describe('D', function() { this.it('5', angular.noop); }); }); }); }); var specs = root.getSpecs(); expect(specs.length).toEqual(3); expect(specs[0].name).toEqual('5'); expect(specs[1].name).toEqual('4'); expect(specs[2].name).toEqual('2'); }); it('should create uniqueIds in the tree', function() { angular.scenario.Describe.id = 0; var a = new angular.scenario.Describe(); var b = new angular.scenario.Describe(); expect(a.id).toNotEqual(b.id); }); it('should create uniqueIds for each spec', function() { var d = new angular.scenario.Describe(); d.it('fake', function() {}); d.it('fake', function() {}); expect(d.its[0].id).toBeDefined(); expect(d.its[1].id).toBeDefined(); expect(d.its[0].id).not.toEqual(d.its[1].id); }); }); angular.js-1.2.11/test/ngScenario/FutureSpec.js000066400000000000000000000046231227375216300213470ustar00rootroot00000000000000'use strict'; describe('angular.scenario.Future', function() { var future; it('should set the sane defaults', function() { var behavior = function() {}; var future = new angular.scenario.Future('test name', behavior, 'foo'); expect(future.name).toEqual('test name'); expect(future.behavior).toEqual(behavior); expect(future.line).toEqual('foo'); expect(future.value).toBeUndefined(); expect(future.fulfilled).toBeFalsy(); expect(future.parser).toEqual(angular.identity); }); it('should be fulfilled after execution and done callback', function() { var future = new angular.scenario.Future('test name', function(done) { done(); }); future.execute(angular.noop); expect(future.fulfilled).toBeTruthy(); }); it('should take callback with (error, result) and forward', function() { var future = new angular.scenario.Future('test name', function(done) { done(10, 20); }); future.execute(function(error, result) { expect(error).toEqual(10); expect(result).toEqual(20); }); }); it('should use error as value if provided', function() { var future = new angular.scenario.Future('test name', function(done) { done(10, 20); }); future.execute(angular.noop); expect(future.value).toEqual(10); }); it('should parse json with fromJson', function() { var future = new angular.scenario.Future('test name', function(done) { done(null, '{"test": "foo"}'); }); future.fromJson().execute(angular.noop); expect(future.value).toEqual({test: 'foo'}); }); it('should convert to json with toJson', function() { var future = new angular.scenario.Future('test name', function(done) { done(null, {test: 'foo'}); }); future.toJson().execute(angular.noop); expect(future.value).toEqual('{"test":"foo"}'); }); it('should convert with custom parser', function() { var future = new angular.scenario.Future('test name', function(done) { done(null, 'foo'); }); future.parsedWith(function(value) { return value.toUpperCase(); }).execute(angular.noop); expect(future.value).toEqual('FOO'); }); it('should pass error if parser fails', function() { var future = new angular.scenario.Future('test name', function(done) { done(null, '{'); }); future.fromJson().execute(function(error, result) { expect(error).toBeDefined(); }); }); }); angular.js-1.2.11/test/ngScenario/ObjectModelSpec.js000066400000000000000000000254241227375216300222660ustar00rootroot00000000000000'use strict'; describe('angular.scenario.ObjectModel', function() { var model; var runner; var spec, step; function buildSpec(id, name, definitions) { var spec = { id: id, name: name, definition: { name: definitions.shift() } }; var currentDef = spec.definition; forEach(definitions, function(defName) { currentDef.parent = { name: defName }; currentDef = currentDef.parent; }); return spec; } function buildStep(name, line) { return { name: name || 'test step', line: function() { return line || ''; } }; } beforeEach(function() { spec = buildSpec(1, 'test spec', ['describe 1']); step = buildStep(); runner = new angular.scenario.testing.MockRunner(); model = new angular.scenario.ObjectModel(runner); }); it('should value default empty value', function() { expect(model.value).toEqual({ name: '', children: [] }); }); it('should add spec and create describe blocks on SpecBegin event', function() { runner.emit('SpecBegin', buildSpec(1, 'test spec', ['describe 2', 'describe 1'])); expect(model.value.children['describe 1']).toBeDefined(); expect(model.value.children['describe 1'].children['describe 2']).toBeDefined(); expect(model.value.children['describe 1'].children['describe 2'].specs['test spec']).toBeDefined(); }); it('should set fullDefinitionName on SpecBegin event', function() { runner.emit('SpecBegin', buildSpec(1, 'fake spec', ['describe 2'])); var spec = model.getSpec(1); expect(spec.fullDefinitionName).toBeDefined(); expect(spec.fullDefinitionName).toEqual('describe 2'); }); it('should set fullDefinitionName on SpecBegin event (join more names by space)', function() { runner.emit('SpecBegin', buildSpec(1, 'fake spec', ['describe 2', 'describe 1'])); var spec = model.getSpec(1); expect(spec.fullDefinitionName).toEqual('describe 1 describe 2'); }); it('should add step to spec on StepBegin', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].steps.length).toEqual(1); }); it('should update spec timer duration on SpecEnd event', function() { runner.emit('SpecBegin', spec); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].duration).toBeDefined(); }); it('should update step timer duration on StepEnd event', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].steps[0].duration).toBeDefined(); }); it('should set spec status on SpecEnd to success if no status set', function() { runner.emit('SpecBegin', spec); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('success'); }); it('should set status to error after SpecError', function() { runner.emit('SpecBegin', spec); runner.emit('SpecError', spec, 'error'); expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('error'); }); it('should set spec status to failure if step fails', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('failure'); }); it('should set spec status to error if step errors', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepError', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('error'); }); describe('events', function() { var Spec = angular.scenario.ObjectModel.Spec, Step = angular.scenario.ObjectModel.Step, callback; beforeEach(function() { callback = jasmine.createSpy('listener'); }); it('should provide method for registering a listener', function() { expect(model.on).toBeDefined(); expect(model.on instanceof Function).toBe(true); }); it('should forward SpecBegin event', function() { model.on('SpecBegin', callback); runner.emit('SpecBegin', spec); expect(callback).toHaveBeenCalled(); }); it('should forward SpecBegin event with ObjectModel.Spec as a param', function() { model.on('SpecBegin', callback); runner.emit('SpecBegin', spec); expect(callback.mostRecentCall.args[0] instanceof Spec).toBe(true); expect(callback.mostRecentCall.args[0].name).toEqual(spec.name); }); it('should forward SpecError event', function() { model.on('SpecError', callback); runner.emit('SpecBegin', spec); runner.emit('SpecError', spec, {}); expect(callback).toHaveBeenCalled(); }); it('should forward SpecError event with ObjectModel.Spec and error as a params', function() { var error = {}; model.on('SpecError', callback); runner.emit('SpecBegin', spec); runner.emit('SpecError', spec, error); var param = callback.mostRecentCall.args[0]; expect(param instanceof Spec).toBe(true); expect(param.name).toEqual(spec.name); expect(param.status).toEqual('error'); expect(param.error).toBe(error); }); it('should forward SpecEnd event', function() { model.on('SpecEnd', callback); runner.emit('SpecBegin', spec); runner.emit('SpecEnd', spec); expect(callback).toHaveBeenCalled(); }); it('should forward SpecEnd event with ObjectModel.Spec as a param', function() { model.on('SpecEnd', callback); runner.emit('SpecBegin', spec); runner.emit('SpecEnd', spec); expect(callback.mostRecentCall.args[0] instanceof Spec).toBe(true); expect(callback.mostRecentCall.args[0].name).toEqual(spec.name); }); it('should forward StepBegin event', function() { model.on('StepBegin', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); expect(callback).toHaveBeenCalled(); }); it('should forward StepBegin event with Spec and Step as params', function() { model.on('StepBegin', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); var params = callback.mostRecentCall.args; expect(params[0] instanceof Spec).toBe(true); expect(params[0].name).toEqual(spec.name); expect(params[1] instanceof Step).toBe(true); }); it('should forward StepError event', function() { model.on('StepError', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepError', spec, step, {}); expect(callback).toHaveBeenCalled(); }); it('should forward StepError event with Spec, Step and error as params', function() { var error = {}; model.on('StepError', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepError', spec, step, error); var params = callback.mostRecentCall.args; expect(params[0] instanceof Spec).toBe(true); expect(params[0].name).toEqual(spec.name); expect(params[1] instanceof Step).toBe(true); expect(params[1].status).toEqual('error'); expect(params[2]).toBe(error); }); it('should forward StepFailure event', function() { model.on('StepFailure', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, {}); expect(callback).toHaveBeenCalled(); }); it('should forward StepFailure event with Spec, Step and error as params', function() { var error = {}; model.on('StepFailure', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, error); var params = callback.mostRecentCall.args; expect(params[0] instanceof Spec).toBe(true); expect(params[0].name).toEqual(spec.name); expect(params[1] instanceof Step).toBe(true); expect(params[1].status).toEqual('failure'); expect(params[2]).toBe(error); }); it('should forward StepEnd event', function() { model.on('StepEnd', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); expect(callback).toHaveBeenCalled(); }); it('should forward StepEnd event with Spec and Step as params', function() { model.on('StepEnd', callback); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); var params = callback.mostRecentCall.args; expect(params[0] instanceof Spec).toBe(true); expect(params[0].name).toEqual(spec.name); expect(params[1] instanceof Step).toBe(true); }); it('should forward RunnerEnd event', function() { model.on('RunnerEnd', callback); runner.emit('RunnerEnd'); expect(callback).toHaveBeenCalled(); }); it('should set error of first failure', function() { var error = 'first-error', step2 = buildStep(); model.on('SpecEnd', function(spec) { expect(spec.error).toBeDefined(); expect(spec.error).toBe(error); }); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, error); runner.emit('StepBegin', spec, step2); runner.emit('StepFailure', spec, step2, 'second-error'); runner.emit('SpecEnd', spec); }); it('should set line number of first failure', function() { var step = buildStep('fake', 'first-line'), step2 = buildStep('fake2', 'second-line'); model.on('SpecEnd', function(spec) { expect(spec.line).toBeDefined(); expect(spec.line).toBe('first-line'); }); runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, null); runner.emit('StepBegin', spec, step2); runner.emit('StepFailure', spec, step2, null); runner.emit('SpecEnd', spec); }); }); }); angular.js-1.2.11/test/ngScenario/RunnerSpec.js000066400000000000000000000067121227375216300213470ustar00rootroot00000000000000'use strict'; /** * Mock spec runner. */ function MockSpecRunner() {} MockSpecRunner.prototype.run = function(spec, specDone) { spec.before.call(this); spec.body.call(this); spec.after.call(this); specDone(); }; MockSpecRunner.prototype.addFuture = function(name, fn, line) { return {name: name, fn: fn, line: line}; }; describe('angular.scenario.Runner', function() { var $window; var runner; beforeEach(function() { // Trick to get the scope out of a DSL statement angular.scenario.dsl('dslAddFuture', function() { return function() { return this.addFuture('future name', angular.noop); }; }); // Trick to get the scope out of a DSL statement angular.scenario.dsl('dslScope', function() { var scope = this; return function() { return scope; }; }); // Trick to get the scope out of a DSL statement angular.scenario.dsl('dslChain', function() { return function() { this.chained = 0; this.chain = function() { this.chained++; return this; }; return this; }; }); $window = { location: {} }; runner = new angular.scenario.Runner($window); runner.on('SpecError', angular.mock.rethrow); runner.on('StepError', angular.mock.rethrow); }); afterEach(function() { delete angular.scenario.dsl.dslScope; delete angular.scenario.dsl.dslChain; }); it('should publish the functions in the public API', function() { angular.forEach(runner.api, function(fn, name) { var func; if (name in $window) { func = $window[name]; } expect(angular.isFunction(func)).toBeTruthy(); }); }); it('should construct valid describe trees with public API', function() { var before = []; var after = []; $window.describe('A', function() { $window.beforeEach(function() { before.push('A'); }); $window.afterEach(function() { after.push('A'); }); $window.it('1', angular.noop); $window.describe('B', function() { $window.beforeEach(function() { before.push('B'); }); $window.afterEach(function() { after.push('B'); }); $window.it('2', angular.noop); $window.describe('C', function() { $window.beforeEach(function() { before.push('C'); }); $window.afterEach(function() { after.push('C'); }); $window.it('3', angular.noop); }); }); }); var specs = runner.rootDescribe.getSpecs(); specs[0].before(); specs[0].body(); specs[0].after(); expect(before).toEqual(['A', 'B', 'C']); expect(after).toEqual(['C', 'B', 'A']); expect(specs[2].definition.parent).toEqual(runner.rootDescribe); expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]); }); it('should publish the DSL statements to the $window', function() { $window.describe('describe', function() { $window.it('1', function() { expect($window.dslScope).toBeDefined(); }); }); runner.run(null/*application*/); }); it('should create a new scope for each DSL chain', function() { $window.describe('describe', function() { $window.it('1', function() { var scope = $window.dslScope(); scope.test = "foo"; expect($window.dslScope().test).toBeUndefined(); }); $window.it('2', function() { var scope = $window.dslChain().chain().chain(); expect(scope.chained).toEqual(2); }); }); runner.run(null/*application*/); }); }); angular.js-1.2.11/test/ngScenario/ScenarioSpec.js000066400000000000000000000025701227375216300216370ustar00rootroot00000000000000'use strict'; describe("ScenarioSpec: Compilation", function() { var element; afterEach(function() { dealoc(element); }); describe('compilation', function() { it("should compile dom node and return scope", inject(function($rootScope, $compile) { var node = jqLite('
                      {{b=a+1}}
                      ')[0]; element = $compile(node)($rootScope); $rootScope.$digest(); expect($rootScope.a).toEqual(1); expect($rootScope.b).toEqual(2); })); it("should compile jQuery node and return scope", inject(function($rootScope, $compile) { element = $compile(jqLite('
                      {{a=123}}
                      '))($rootScope); $rootScope.$digest(); expect(jqLite(element).text()).toEqual('123'); })); it("should compile text node and return scope", inject(function($rootScope, $compile) { element = $compile('
                      {{a=123}}
                      ')($rootScope); $rootScope.$digest(); expect(jqLite(element).text()).toEqual('123'); })); }); describe('jQuery', function () { it('should exist on the angular.scenario object', function () { expect(angular.scenario.jQuery).toBeDefined(); }); it('should have common jQuery methods', function () { var jQuery = angular.scenario.jQuery; expect(typeof jQuery).toEqual('function'); expect(typeof jQuery('
                      ').html).toEqual('function'); }) }); }); angular.js-1.2.11/test/ngScenario/SpecRunnerSpec.js000066400000000000000000000101601227375216300221520ustar00rootroot00000000000000'use strict'; /** * Mock Application */ function ApplicationMock($window) { this.$window = $window; } ApplicationMock.prototype = { executeAction: function(callback) { callback.call(this.$window, _jQuery(this.$window.document), this.$window); } }; describe('angular.scenario.SpecRunner', function() { var $window, $root, log; var runner; function createSpec(name, body) { return { name: name, before: angular.noop, body: body || angular.noop, after: angular.noop }; } beforeEach(inject(function($rootScope) { log = []; $window = {}; $window.setTimeout = function(fn, timeout) { fn(); }; $root = $rootScope; $root.emit = function(eventName) { log.push(eventName); }; $root.on = function(eventName) { log.push('Listener Added for ' + eventName); }; $root.application = new ApplicationMock($window); $root.$window = $window; runner = $root.$new(); var Cls = angular.scenario.SpecRunner; for (var name in Cls.prototype) runner[name] = angular.bind(runner, Cls.prototype[name]); Cls.call(runner); })); it('should bind futures to the spec', function() { runner.addFuture('test future', function(done) { this.value = 10; done(); }); runner.futures[0].execute(angular.noop); expect(runner.value).toEqual(10); }); it('should pass done to future action behavior', function() { runner.addFutureAction('test future', function($window, $document, done) { expect(angular.isFunction(done)).toBeTruthy(); done(10, 20); }); runner.futures[0].execute(function(error, result) { expect(error).toEqual(10); expect(result).toEqual(20); }); }); it('should execute spec function and notify UI', function() { var finished; var spec = createSpec('test spec', function() { this.test = 'some value'; }); runner.addFuture('test future', function(done) { done(); }); runner.run(spec, function() { finished = true; }); expect(runner.test).toEqual('some value'); expect(finished).toBeTruthy(); expect(log).toEqual([ 'SpecBegin', 'StepBegin', 'StepEnd', 'SpecEnd' ]); }); it('should execute notify UI on spec setup error', function() { var finished; var spec = createSpec('test spec', function() { throw 'message'; }); runner.run(spec, function() { finished = true; }); expect(finished).toBeTruthy(); expect(log).toEqual([ 'SpecBegin', 'SpecError', 'SpecEnd' ]); }); it('should execute notify UI on step failure', function() { var finished; var spec = createSpec('test spec'); runner.addFuture('test future', function(done) { done('failure message'); }); runner.run(spec, function() { finished = true; }); expect(finished).toBeTruthy(); expect(log).toEqual([ 'SpecBegin', 'StepBegin', 'StepFailure', 'StepEnd', 'SpecEnd' ]); }); it('should execute notify UI on step error', function() { var finished; var spec = createSpec('test spec', function() { this.addFuture('test future', function(done) { throw 'error message'; }); }); runner.run(spec, function() { finished = true; }); expect(finished).toBeTruthy(); expect(log).toEqual([ 'SpecBegin', 'StepBegin', 'StepError', 'StepEnd', 'SpecEnd' ]); }); it('should run after handlers even if error in body of spec', function() { var finished, after; var spec = createSpec('test spec', function() { this.addFuture('body', function(done) { throw 'error message'; }); }); spec.after = function() { this.addFuture('after', function(done) { after = true; done(); }); }; runner.run(spec, function() { finished = true; }); expect(finished).toBeTruthy(); expect(after).toBeTruthy(); expect(log).toEqual([ 'SpecBegin', 'StepBegin', 'StepError', 'StepEnd', 'StepBegin', 'StepEnd', 'SpecEnd' ]); }); }); angular.js-1.2.11/test/ngScenario/dslSpec.js000066400000000000000000000654651227375216300206720ustar00rootroot00000000000000'use strict'; describe("angular.scenario.dsl", function() { var element; var $window, $root; var eventLog; afterEach(function() { dealoc(element); }); beforeEach(module('ngSanitize')); beforeEach(inject(function($injector) { eventLog = []; $window = { document: window.document.body, angular: new angular.scenario.testing.MockAngular() }; jqLite($window.document).data('$injector', $injector).attr('ng-app', '').addClass('html'); $root = $injector.get('$rootScope'); $root.emit = function(eventName) { eventLog.push(eventName); }; $root.on = function(eventName) { eventLog.push('Listener Added for ' + eventName); }; $root.futures = []; $root.futureLog = []; $root.$window = $window; $root.addFuture = function(name, fn) { this.futures.push(name); fn.call(this, function(error, result) { $root.futureError = error; $root.futureResult = result; $root.futureLog.push(name); }); }; $root.dsl = {}; angular.forEach(angular.scenario.dsl, function(fn, name) { $root.dsl[name] = function() { return fn.call($root).apply($root, arguments); }; }); $root.application = new angular.scenario.Application(jqLite($window.document)); $root.application.getWindow_ = function() { return $window; }; $root.application.navigateTo = function(url, callback) { $window.location = url; callback(); }; // Just use the real one since it delegates to this.addFuture $root.addFutureAction = angular.scenario. SpecRunner.prototype.addFutureAction; jqLite($window.document).empty(); })); afterEach(function(){ jqLite($window.document).removeData('$injector'); }); describe('Pause', function() { it('should pause until resume to complete', function() { expect($window.resume).toBeUndefined(); $root.dsl.pause(); expect(angular.isFunction($window.resume)).toBeTruthy(); expect($root.futureLog).toEqual([]); $window.resume(); expect($root.futureLog). toEqual(['pausing for you to resume']); expect(eventLog).toContain('InteractivePause'); }); }); describe('Sleep', function() { beforeEach(function() { $root.$window.setTimeout = function(fn, value) { $root.timerValue = value; fn(); }; }); it('should sleep for specified seconds', function() { $root.dsl.sleep(10); expect($root.timerValue).toEqual(10000); expect($root.futureResult).toEqual(10000); }); }); describe('Expect', function() { it('should chain and execute matcher', function() { var future = {value: 10}; var result = $root.dsl.expect(future); result.toEqual(10); expect($root.futureError).toBeUndefined(); expect($root.futureResult).toBeUndefined(); result = $root.dsl.expect(future); result.toEqual(20); expect($root.futureError).toBeDefined(); }); }); describe('Browser', function() { describe('Reload', function() { it('should navigateTo', function() { $window.location = { href: '#foo' }; $root.dsl.browser().reload(); expect($root.futureResult).toEqual('#foo'); expect($window.location).toEqual('#foo'); }); }); describe('NavigateTo', function() { it('should allow a string url', function() { $root.dsl.browser().navigateTo('http://myurl'); expect($window.location).toEqual('http://myurl'); expect($root.futureResult).toEqual('http://myurl'); }); it('should allow a future url', function() { $root.dsl.browser().navigateTo('http://myurl', function() { return 'http://futureUrl/'; }); expect($window.location).toEqual('http://futureUrl/'); expect($root.futureResult).toEqual('http://futureUrl/'); }); it('should complete if angular is missing from app frame', function() { delete $window.angular; $root.dsl.browser().navigateTo('http://myurl'); expect($window.location).toEqual('http://myurl'); expect($root.futureResult).toEqual('http://myurl'); }); }); describe('window', function() { beforeEach(function() { $window.location = { href: 'http://myurl/some/path?foo=10#/bar?x=2', pathname: '/some/path', search: '?foo=10', hash: '#bar?x=2' }; }); it('should return full URL for href', function() { $root.dsl.browser().window().href(); expect($root.futureResult).toEqual($window.location.href); }); it('should return the pathname', function() { $root.dsl.browser().window().path(); expect($root.futureResult).toEqual($window.location.pathname); }); it('should return the search part', function() { $root.dsl.browser().window().search(); expect($root.futureResult).toEqual($window.location.search); }); it('should return the hash without the #', function() { $root.dsl.browser().window().hash(); expect($root.futureResult).toEqual('bar?x=2'); }); }); describe('location', function() { beforeEach(inject(function($injector) { angular.extend($injector.get('$location'), { url: function() {return '/path?search=a#hhh';}, path: function() {return '/path';}, search: function() {return {search: 'a'};}, hash: function() {return 'hhh';} }); })); it('should return full url', function() { $root.dsl.browser().location().url(); expect($root.futureResult).toEqual('/path?search=a#hhh'); }); it('should return the pathname', function() { $root.dsl.browser().location().path(); expect($root.futureResult).toEqual('/path'); }); it('should return the query string as an object', function() { $root.dsl.browser().location().search(); expect($root.futureResult).toEqual({search: 'a'}); }); it('should return the hash without the #', function() { $root.dsl.browser().location().hash(); expect($root.futureResult).toEqual('hhh'); }); }); }); describe('Element Finding', function() { var doc; beforeEach(inject(function($injector) { doc = _jQuery($window.document).append('
                      ').find('.body'); })); describe('Select', function() { it('should select single option', function() { doc.append( '' ); $root.dsl.select('test').option('A'); expect(doc.find('[ng-model="test"]').val()).toEqual('A'); }); it('should select single option using data-ng', function() { doc.append( '' ); $root.dsl.select('test').option('A'); expect(doc.find('[data-ng-model="test"]').val()).toEqual('A'); }); it('should select single option using x-ng', function() { doc.append( '' ); $root.dsl.select('test').option('A'); expect(doc.find('[x-ng-model="test"]').val()).toEqual('A'); }); it('should select option by exact name', function() { doc.append( '' ); $root.dsl.select('test').option('one'); expect(doc.find('[ng-model="test"]').val()).toEqual('D'); }); it('should select option by name if no exact match and name contains value', function() { doc.append( '' ); $root.dsl.select('test').option('one'); expect(doc.find('[ng-model="test"]').val()).toEqual('A'); }); it('should select multiple options', function() { doc.append( '' ); $root.dsl.select('test').options('A', 'B'); expect(doc.find('[ng-model="test"]').val()).toEqual(['A','B']); }); it('should fail to select multiple options on non-multiple select', function() { doc.append(''); $root.dsl.select('test').options('A', 'B'); expect($root.futureError).toMatch(/did not match/); }); it('should fail to select an option that does not exist', function(){ doc.append( '' ); $root.dsl.select('test').option('three'); expect($root.futureError).toMatch(/not found/); }); }); describe('Element', function() { it('should execute click', function() { var clicked; // Hash is important, otherwise we actually // go to a different page and break the runner doc.append(''); doc.find('a').click(function() { clicked = true; }); $root.dsl.element('a').click(); }); it('should navigate page if click on anchor', function() { expect($window.location).not.toEqual('#foo'); doc.append(''); $root.dsl.element('a').click(); expect($window.location).toMatch(/#foo$/); }); it('should not navigate if click event was cancelled', function() { var initLocation = $window.location, elm = jqLite(''); doc.append(elm); elm.on('click', function(event) { event.preventDefault(); }); $root.dsl.element('a').click(); expect($window.location).toBe(initLocation); dealoc(elm); }); it('should execute dblclick', function() { var clicked; // Hash is important, otherwise we actually // go to a different page and break the runner doc.append(''); doc.find('a').dblclick(function() { clicked = true; }); $root.dsl.element('a').dblclick(); }); it('should navigate page if dblclick on anchor', function() { expect($window.location).not.toEqual('#foo'); doc.append(''); $root.dsl.element('a').dblclick(); expect($window.location).toMatch(/#foo$/); }); it('should not navigate if dblclick event was cancelled', function() { var initLocation = $window.location, elm = jqLite(''); doc.append(elm); elm.on('dblclick', function(event) { event.preventDefault(); }); $root.dsl.element('a').dblclick(); expect($window.location).toBe(initLocation); dealoc(elm); }); it('should execute mouseover', function() { var mousedOver; doc.append('
                      '); doc.find('div').mouseover(function() { mousedOver = true; }); $root.dsl.element('div').mouseover(); expect(mousedOver).toBe(true); }); it('should bubble up the mouseover event', function() { var mousedOver; doc.append('
                      '); doc.find('#outer').mouseover(function() { mousedOver = true; }); $root.dsl.element('#inner').mouseover(); expect(mousedOver).toBe(true); }); it('should execute mousedown', function() { var mousedDown; doc.append('
                      '); doc.find('div').mousedown(function() { mousedDown = true; }); $root.dsl.element('div').mousedown(); expect(mousedDown).toBe(true); }); it('should bubble up the mousedown event', function() { var mousedDown; doc.append('
                      '); doc.find('#outer').mousedown(function() { mousedDown = true; }); $root.dsl.element('#inner').mousedown(); expect(mousedDown).toBe(true); }); it('should execute mouseup', function() { var mousedUp; doc.append('
                      '); doc.find('div').mouseup(function() { mousedUp = true; }); $root.dsl.element('div').mouseup(); expect(mousedUp).toBe(true); }); it('should bubble up the mouseup event', function() { var mousedUp; doc.append('
                      '); doc.find('#outer').mouseup(function() { mousedUp = true; }); $root.dsl.element('#inner').mouseup(); expect(mousedUp).toBe(true); }); it('should count matching elements', function() { doc.append(''); $root.dsl.element('span').count(); expect($root.futureResult).toEqual(2); }); it('should return count of 0 if no matching elements', function() { $root.dsl.element('span').count(); expect($root.futureResult).toEqual(0); }); it('should get attribute', function() { doc.append('
                      '); $root.dsl.element('#test').attr('class'); expect($root.futureResult).toEqual('foo'); }); it('should set attribute', function() { doc.append('
                      '); $root.dsl.element('#test').attr('class', 'bam'); expect(doc.find('#test').attr('class')).toEqual('bam'); }); it('should get property', function() { doc.append('
                      '); $root.dsl.element('#test').prop('className'); expect($root.futureResult).toEqual('foo'); }); it('should set property', function() { doc.append('
                      '); $root.dsl.element('#test').prop('className', 'bam'); expect(doc.find('#test').prop('className')).toEqual('bam'); }); it('should get css', function() { doc.append('
                      '); $root.dsl.element('#test').css('height'); expect($root.futureResult).toMatch(/30px/); }); it('should set css', function() { doc.append('
                      '); $root.dsl.element('#test').css('height', '20px'); expect(doc.find('#test').css('height')).toEqual('20px'); }); it('should add all jQuery key/value methods', function() { var METHODS = ['css', 'attr']; var chain = $root.dsl.element('input'); angular.forEach(METHODS, function(name) { expect(angular.isFunction(chain[name])).toBeTruthy(); }); }); it('should get val', function() { doc.append(''); $root.dsl.element('input').val(); expect($root.futureResult).toEqual('bar'); }); it('should set val', function() { doc.append(''); $root.dsl.element('input').val('baz'); expect(doc.find('input').val()).toEqual('baz'); }); it('should use correct future name for generated set methods', function() { doc.append(''); $root.dsl.element('input').val(false); expect($root.futures.pop()).toMatch(/element 'input' set val/); }); it('should use correct future name for generated get methods', function() { doc.append(''); $root.dsl.element('input').val(); expect($root.futures.pop()).toMatch(/element 'input' val/); }); it('should add all jQuery property methods', function() { var METHODS = [ 'val', 'text', 'html', 'height', 'innerHeight', 'outerHeight', 'width', 'innerWidth', 'outerWidth', 'position', 'scrollLeft', 'scrollTop', 'offset' ]; var chain = $root.dsl.element('input'); angular.forEach(METHODS, function(name) { expect(angular.isFunction(chain[name])).toBeTruthy(); }); }); it('should execute custom query', function() { doc.append(''); $root.dsl.element('#test').query(function(elements, done) { done(null, elements.attr('href')); }); expect($root.futureResult).toEqual('http://example.com/myUrl'); }); it('should use the selector as label if none is given', function() { $root.dsl.element('mySelector'); expect($root.label).toEqual('mySelector'); }); it('should include the selector in paren when a label is given', function() { $root.dsl.element('mySelector', 'myLabel'); expect($root.label).toEqual('myLabel ( mySelector )'); }); }); describe('Repeater', function() { var chain; beforeEach(inject(function($compile, $rootScope) { element = $compile( '
                      • {{i.name}} {{i.gender}}
                      ')($rootScope); $rootScope.items = [{name:'misko', gender:'male'}, {name:'felisa', gender:'female'}]; $rootScope.$apply(); doc.append(element); chain = $root.dsl.repeater('ul li'); })); it('should get the row count', function() { chain.count(); expect($root.futureResult).toEqual(2); }); it('should return 0 if repeater doesnt match', inject(function($rootScope) { $rootScope.items = []; $rootScope.$apply(); chain.count(); expect($root.futureResult).toEqual(0); })); it('should get a row of bindings', function() { chain.row(1); expect($root.futureResult).toEqual(['felisa', 'female']); }); it('should get a column of bindings', function() { chain.column('i.gender'); expect($root.futureResult).toEqual(['male', 'female']); }); it('should use the selector as label if none is given', function() { expect($root.label).toEqual('ul li'); }); it('should include the selector in paren when a label is given', function() { $root.dsl.repeater('mySelector', 'myLabel'); expect($root.label).toEqual('myLabel ( ul li mySelector )'); }); }); describe('Binding', function() { var compile; beforeEach(inject(function($compile, $rootScope) { compile = function(html, value) { element = $compile(html)($rootScope); doc.append(element); $rootScope.foo = {bar: value || 'some value'}; $rootScope.$apply(); }; })); it('should select binding in interpolation', function() { compile('{{ foo.bar }}'); $root.dsl.binding('foo.bar'); expect($root.futureResult).toEqual('some value'); }); it('should select binding in multiple interpolations', function() { compile('{{ foo.bar }}
                      {{ true }}
                      '); $root.dsl.binding('foo.bar'); expect($root.futureResult).toEqual('some value'); $root.dsl.binding('true'); expect($root.futureResult).toEqual('true'); }); it('should select binding by name', function() { compile(''); $root.dsl.binding('foo.bar'); expect($root.futureResult).toEqual('some value'); }); it('should select binding by regexp', function() { compile('some value'); $root.dsl.binding(/^foo\..+/); expect($root.futureResult).toEqual('some value'); }); it('should return innerHTML for all the other elements', function() { compile('
                      ', 'some value'); $root.dsl.binding('foo.bar'); expect($root.futureResult.toLowerCase()).toEqual('some value'); }); it('should select binding in template by name', function() { compile('
                      ', 'bar');
                              $root.dsl.binding('foo.bar');
                              expect($root.futureResult).toEqual('bar');
                            });
                      
                            it('should match bindings by substring match', function() {
                              compile('
                      ', 'binding value');
                              $root.dsl.binding('foo . bar');
                              expect($root.futureResult).toEqual('binding value');
                            });
                      
                            it('should return error if no bindings in document', function() {
                              $root.dsl.binding('foo.bar');
                              expect($root.futureError).toMatch(/did not match/);
                            });
                      
                            it('should return error if no binding matches', function() {
                              compile('some value');
                              $root.dsl.binding('foo.bar');
                              expect($root.futureError).toMatch(/did not match/);
                            });
                          });
                      
                          describe('Using', function() {
                            it('should prefix selector in $document.elements()', function() {
                              var chain;
                              doc.append(
                                '
                      ' + '
                      ' ); chain = $root.dsl.using('div#test2'); chain.input('test.input').enter('foo'); var inputs = _jQuery('input[ng-model="test.input"]'); expect(inputs.first().val()).toEqual('something'); expect(inputs.last().val()).toEqual('foo'); }); it('should use the selector as label if none is given', function() { $root.dsl.using('mySelector'); expect($root.label).toEqual('mySelector'); }); it('should include the selector in paren when a label is given', function() { $root.dsl.using('mySelector', 'myLabel'); expect($root.label).toEqual('myLabel ( mySelector )'); }); }); describe('Input', function() { it('should change value in text input', inject(function($compile) { runs(function() { element = $compile('')($root); doc.append(element); var chain = $root.dsl.input('test.input'); chain.enter('foo'); expect(_jQuery('input[ng-model="test.input"]').val()).toEqual('foo'); }); // cleanup the event queue waits(0); runs(function() { expect($root.test.input).toBe('foo'); }); })); it('should change value in text input in dash form', function() { doc.append(''); var chain = $root.dsl.input('test.input'); chain.enter('foo'); expect(_jQuery('input[ng-model="test.input"]').val()).toEqual('foo'); }); it('should change value in text input in data-ng form', function() { doc.append(''); var chain = $root.dsl.input('test.input'); chain.enter('foo'); expect(_jQuery('input[data-ng-model="test.input"]').val()).toEqual('foo'); }); it('should change value in text input in x-ng form', function() { doc.append(''); var chain = $root.dsl.input('test.input'); chain.enter('foo'); expect(_jQuery('input[x-ng-model="test.input"]').val()).toEqual('foo'); }); it('should return error if no input exists', function() { var chain = $root.dsl.input('test.input'); chain.enter('foo'); expect($root.futureError).toMatch(/did not match/); }); it('should toggle checkbox state', function() { doc.append(''); expect(_jQuery('input[ng-model="test.input"]'). prop('checked')).toBe(true); var chain = $root.dsl.input('test.input'); chain.check(); expect(_jQuery('input[ng-model="test.input"]'). prop('checked')).toBe(false); $window.angular.reset(); chain.check(); expect(_jQuery('input[ng-model="test.input"]'). prop('checked')).toBe(true); }); it('should return error if checkbox did not match', function() { var chain = $root.dsl.input('test.input'); chain.check(); expect($root.futureError).toMatch(/did not match/); }); it('should select option from radio group', function() { doc.append( '' + '' ); // HACK! We don't know why this is sometimes false on chrome _jQuery('input[ng\\:model="test.input"][value="bar"]').prop('checked', true); expect(_jQuery('input[ng\\:model="test.input"][value="bar"]'). prop('checked')).toBe(true); expect(_jQuery('input[ng\\:model="test.input"][value="foo"]'). prop('checked')).toBe(false); var chain = $root.dsl.input('test.input'); chain.select('foo'); expect(_jQuery('input[ng\\:model="test.input"][value="bar"]'). prop('checked')).toBe(false); expect(_jQuery('input[ng\\:model="test.input"][value="foo"]'). prop('checked')).toBe(true); }); it('should return error if radio button did not match', function() { var chain = $root.dsl.input('test.input'); chain.select('foo'); expect($root.futureError).toMatch(/did not match/); }); describe('val', function() { it('should return value in text input', function() { doc.append(''); $root.dsl.input('test.input').val(); expect($root.futureResult).toEqual("something"); }); }); }); describe('Textarea', function() { it('should change value in textarea', function() { doc.append(''); var chain = $root.dsl.input('test.textarea'); chain.enter('foo'); expect(_jQuery('textarea[ng-model="test.textarea"]').val()).toEqual('foo'); }); it('should return error if no textarea exists', function() { var chain = $root.dsl.input('test.textarea'); chain.enter('foo'); expect($root.futureError).toMatch(/did not match/); }); }); }); }); angular.js-1.2.11/test/ngScenario/e2e/000077500000000000000000000000001227375216300173725ustar00rootroot00000000000000angular.js-1.2.11/test/ngScenario/e2e/Runner-compiled.html000066400000000000000000000006111227375216300233210ustar00rootroot00000000000000 angular.js-1.2.11/test/ngScenario/e2e/Runner.html000066400000000000000000000006721227375216300215360ustar00rootroot00000000000000 angular.js-1.2.11/test/ngScenario/e2e/style.css000066400000000000000000000001441227375216300212430ustar00rootroot00000000000000th { text-align: left; } tr { border: 1px solid black; } .redbox { background-color: red; } angular.js-1.2.11/test/ngScenario/e2e/widgets-scenario.js000066400000000000000000000051551227375216300232050ustar00rootroot00000000000000'use strict'; describe('widgets', function() { it('should verify that basic widgets work', function() { browser().navigateTo('widgets.html'); using('#text-basic-box').input('text.basic').enter('Carlos'); expect(binding('text.basic')).toEqual('Carlos'); input('text.basic').enter('Carlos Santana'); expect(binding('text.basic')).not().toEqual('Carlos Boozer'); input('text.password').enter('secret'); expect(binding('text.password')).toEqual('secret'); expect(binding('text.hidden')).toEqual('hiddenValue'); expect(binding('gender')).toEqual('male'); input('gender').select('female'); expect(using('#gender-box').binding('gender')).toEqual('female'); expect(repeater('#repeater-row ul li').count()).toEqual(2); expect(repeater('#repeater-row ul li').row(1)).toEqual(['adam']); expect(repeater('#repeater-row ul li').column('name')).toEqual(['misko', 'adam']); select('select').option('B'); expect(binding('select')).toEqual('B'); select('multiselect').options('A', 'C'); expect(binding('multiselect').fromJson()).toEqual(['A', 'C']); expect(binding('button').fromJson()).toEqual({'count': 0}); expect(binding('form').fromJson()).toEqual({'count': 0}); element('form a', "'action' link").click(); expect(binding('button').fromJson()).toEqual({'count': 1}); element('input[value="submit input"]', "'submit input' button").click(); expect(binding('button').fromJson()).toEqual({'count': 2}); expect(binding('form').fromJson()).toEqual({'count': 1}); element('button:contains("submit button")', "'submit button' button").click(); expect(binding('button').fromJson()).toEqual({'count': 2}); expect(binding('form').fromJson()).toEqual({'count': 2}); element('input[value="button"]', "'button' button").click(); expect(binding('button').fromJson()).toEqual({'count': 3}); element('input[type="image"]', 'form image').click(); expect(binding('button').fromJson()).toEqual({'count': 4}); /** * Custom value parser for futures. */ function checkboxParser(value) { return angular.fromJson(value.substring(value.indexOf('=')+1)); } input('checkbox.tea').check(); expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: false, tea: false}); input('checkbox.coffee').check(); expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: true, tea: false}); input('checkbox.tea').check(); input('checkbox.tea').check(); input('checkbox.tea').check(); expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: true, tea: true}); }); }); angular.js-1.2.11/test/ngScenario/e2e/widgets.html000066400000000000000000000064231227375216300217330ustar00rootroot00000000000000
                      Description Test Result
                      Input text field
                      basic text.basic={{text.basic}}
                      password text.password={{text.password}}
                      hidden text.hidden={{text.hidden}}
                      Input selection field
                      radio Female
                      Male
                      gender={{gender}}
                      checkbox Tea
                      Coffe
                      checkbox={{checkbox}}
                      select select={{select}}
                      multiselect multiselect={{multiselect}}
                      Buttons
                      ng-change
                      ng-click



                      action
                      button={{button}} form={{form}}
                      Repeaters
                      ng-repeat
                      • {{name}}
                      angular.js-1.2.11/test/ngScenario/matchersSpec.js000066400000000000000000000031161227375216300216770ustar00rootroot00000000000000'use strict'; describe('angular.scenario.matchers', function () { var matchers; function expectMatcher(value, test) { delete matchers.error; delete matchers.future.value; if (value !== undefined) { matchers.future.value = value; } test(); expect(matchers.error).toBeUndefined(); } beforeEach(function() { /** * Mock up the future system wrapped around matchers. * * @see Scenario.js#angular.scenario.matcher */ matchers = { future: { name: 'test' } }; matchers.addFuture = function(name, callback) { callback(function(error) { matchers.error = error; }); }; angular.extend(matchers, angular.scenario.matcher); }); it('should handle basic matching', function() { expectMatcher(10, function() { matchers.toEqual(10); }); expectMatcher('value', function() { matchers.toBeDefined(); }); expectMatcher([1], function() { matchers.toBeTruthy(); }); expectMatcher("", function() { matchers.toBeFalsy(); }); expectMatcher(0, function() { matchers.toBeFalsy(); }); expectMatcher('foo', function() { matchers.toMatch('.o.'); }); expectMatcher(null, function() { matchers.toBeNull(); }); expectMatcher([1, 2, 3], function() { matchers.toContain(2); }); expectMatcher(3, function() { matchers.toBeLessThan(10); }); expectMatcher(3, function() { matchers.toBeGreaterThan(-5); }); }); it('should have toHaveClass matcher', function(){ var e = angular.element('
                      '); expect(e).not.toHaveClass('none'); expect(e).toHaveClass('abc'); }); }); angular.js-1.2.11/test/ngScenario/mocks.js000066400000000000000000000015511227375216300203730ustar00rootroot00000000000000'use strict'; angular.scenario.testing = angular.scenario.testing || {}; angular.scenario.testing.MockAngular = function() { this.reset(); this.element = jqLite; }; angular.scenario.testing.MockAngular.prototype.reset = function() { this.log = []; }; angular.scenario.testing.MockAngular.prototype.poll = function() { this.log.push('$brower.poll()'); return this; }; angular.scenario.testing.MockRunner = function() { this.listeners = []; }; angular.scenario.testing.MockRunner.prototype.on = function(eventName, fn) { this.listeners[eventName] = this.listeners[eventName] || []; this.listeners[eventName].push(fn); }; angular.scenario.testing.MockRunner.prototype.emit = function(eventName) { var args = Array.prototype.slice.call(arguments, 1); angular.forEach(this.listeners[eventName] || [], function(fn) { fn.apply(this, args); }); }; angular.js-1.2.11/test/ngScenario/output/000077500000000000000000000000001227375216300202575ustar00rootroot00000000000000angular.js-1.2.11/test/ngScenario/output/HtmlSpec.js000066400000000000000000000076751227375216300223530ustar00rootroot00000000000000'use strict'; describe('angular.scenario.output.html', function() { var runner, model, spec, step, listeners, ui, context; beforeEach(function() { listeners = []; spec = { name: 'test spec', definition: { id: 10, name: 'child', children: [], parent: { id: 20, name: 'parent', children: [] } } }; step = { name: 'some step', line: function() { return 'unknown:-1'; } }; runner = new angular.scenario.testing.MockRunner(); model = new angular.scenario.ObjectModel(runner); context = _jQuery("
                      "); ui = angular.scenario.output.html(context, runner, model); }); it('should create nested describe context', function() { runner.emit('SpecBegin', spec); expect(context.find('#describe-20 #describe-10 > h2').text()). toEqual('describe: child'); expect(context.find('#describe-20 > h2').text()).toEqual('describe: parent'); expect(context.find('#describe-10 .tests > li .test-info .test-name').text()). toEqual('test spec'); expect(context.find('#describe-10 .tests > li').hasClass('status-pending')). toBeTruthy(); }); it('should add link on InteractivePause', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('StepBegin', spec, step); runner.emit('InteractivePause', spec, step); expect(context.find('.test-actions .test-title:first').text()).toEqual('some step'); expect(lowercase(context.find('.test-actions .test-title:last').html())).toEqual( 'paused... resume when ready.' ); }); it('should update totals when steps complete', function() { // Failure for (var i=0; i < 3; ++i) { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); } // Error runner.emit('SpecBegin', spec); runner.emit('SpecError', spec, 'error'); runner.emit('SpecEnd', spec); // Error runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepError', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); // Success runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); expect(parseInt(context.find('#status-legend .status-failure').text(), 10)). toEqual(3); expect(parseInt(context.find('#status-legend .status-error').text(), 10)). toEqual(2); expect(parseInt(context.find('#status-legend .status-success').text(), 10)). toEqual(1); }); it('should update timer when test completes', function() { // Success runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); // Failure runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); // Error runner.emit('SpecBegin', spec); runner.emit('SpecError', spec, 'error'); runner.emit('SpecEnd', spec); context.find('#describe-10 .tests > li .test-info .timer-result'). each(function(index, timer) { expect(timer.innerHTML).toMatch(/ms$/); }); }); it('should include line if provided', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); var errorHtml = context.find('#describe-10 .tests li pre').html(); expect(errorHtml.indexOf('unknown:-1')).toEqual(0); }); }); angular.js-1.2.11/test/ngScenario/output/jsonSpec.js000066400000000000000000000017331227375216300224050ustar00rootroot00000000000000'use strict'; describe('angular.scenario.output.json', function() { var output, context; var runner, model, $window; var spec, step; beforeEach(function() { $window = {}; context = _jQuery('
                      '); runner = new angular.scenario.testing.MockRunner(); model = new angular.scenario.ObjectModel(runner); output = angular.scenario.output.json(context, runner, model); spec = { name: 'test spec', definition: { id: 10, name: 'describe' } }; step = { name: 'some step', line: function() { return 'unknown:-1'; } }; }); it('should put json in context on RunnerEnd', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); runner.emit('RunnerEnd'); expect(angular.fromJson(context.html()).children['describe'] .specs['test spec'].status).toEqual('success'); }); }); angular.js-1.2.11/test/ngScenario/output/objectSpec.js000066400000000000000000000020031227375216300226710ustar00rootroot00000000000000'use strict'; describe('angular.scenario.output.object', function() { var output; var runner, model, $window; var spec, step; beforeEach(function() { $window = {}; runner = new angular.scenario.testing.MockRunner(); model = new angular.scenario.ObjectModel(runner); runner.$window = $window; output = angular.scenario.output.object(null, runner, model); spec = { name: 'test spec', definition: { id: 10, name: 'describe', children: [] } }; step = { name: 'some step', line: function() { return 'unknown:-1'; } }; }); it('should create a global variable $result', function() { expect($window.$result).toBeDefined(); }); it('should maintain live state in $result', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); expect($window.$result.children['describe'] .specs['test spec'].steps[0].duration).toBeDefined(); }); }); angular.js-1.2.11/test/ngScenario/output/xmlSpec.js000066400000000000000000000027471227375216300222420ustar00rootroot00000000000000'use strict'; describe('angular.scenario.output.xml', function() { var output, context; var runner, model, $window; var spec, step; beforeEach(function() { $window = {}; context = _jQuery('
                      '); runner = new angular.scenario.testing.MockRunner(); model = new angular.scenario.ObjectModel(runner); output = angular.scenario.output.xml(context, runner, model); spec = { name: 'test spec', definition: { id: 10, name: 'describe' } }; step = { name: 'some step', line: function() { return 'unknown:-1'; } }; }); it('should create XML nodes for object model', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); runner.emit('RunnerEnd'); expect(context.find('it').attr('status')).toEqual('success'); expect(context.find('it step').attr('status')).toEqual('success'); }); it('should output errors to the XML', function() { runner.emit('SpecBegin', spec); runner.emit('StepBegin', spec, step); runner.emit('StepFailure', spec, step, 'error reason'); runner.emit('StepEnd', spec, step); runner.emit('SpecEnd', spec); runner.emit('RunnerEnd'); expect(context.find('it').attr('status')).toEqual('failure'); expect(context.find('it step').attr('status')).toEqual('failure'); expect(context.find('it step').text()).toEqual('error reason'); }); }); angular.js-1.2.11/test/ngTouch/000077500000000000000000000000001227375216300162365ustar00rootroot00000000000000angular.js-1.2.11/test/ngTouch/directive/000077500000000000000000000000001227375216300202145ustar00rootroot00000000000000angular.js-1.2.11/test/ngTouch/directive/ngClickSpec.js000066400000000000000000000320031227375216300227350ustar00rootroot00000000000000'use strict'; describe('ngClick (touch)', function() { var element, time, orig_now; // TODO(braden): Once we have other touch-friendly browsers on CI, allow them here. // Currently Firefox and IE refuse to fire touch events. var chrome = /chrome/.test(navigator.userAgent.toLowerCase()); if (!chrome) { return; } function mockTime() { return time; } beforeEach(function() { module('ngTouch'); orig_now = Date.now; time = 0; Date.now = mockTime; }); afterEach(function() { dealoc(element); Date.now = orig_now; }); it('should get called on a tap', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.tapped).toBeUndefined(); browserTrigger(element, 'touchstart'); browserTrigger(element, 'touchend'); expect($rootScope.tapped).toEqual(true); })); it('should pass event object', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); browserTrigger(element, 'touchstart'); browserTrigger(element, 'touchend'); expect($rootScope.event).toBeDefined(); })); it('should not click if the touch is held too long', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.count = 0; $rootScope.$digest(); expect($rootScope.count).toBe(0); time = 10; browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 900; browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(0); })); it('should not click if the touchend is too far away', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.tapped).toBeUndefined(); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 400, y: 400 }); expect($rootScope.tapped).toBeUndefined(); })); it('should not click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.tapped).toBeUndefined(); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchmove'); browserTrigger(element, 'touchend',{ keys: [], x: 400, y: 400 }); expect($rootScope.tapped).toBeUndefined(); })); it('should add the CSS class while the element is held down, and then remove it', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.tapped).toBeUndefined(); var CSS_CLASS = 'ng-click-active'; expect(element.hasClass(CSS_CLASS)).toBe(false); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); expect(element.hasClass(CSS_CLASS)).toBe(true); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect(element.hasClass(CSS_CLASS)).toBe(false); expect($rootScope.tapped).toBe(true); })); describe('the clickbuster', function() { var element1, element2; beforeEach(inject(function($rootElement, $document) { $document.find('body').append($rootElement); })); afterEach(inject(function($document) { $document.find('body').empty(); })); it('should cancel the following click event', inject(function($rootScope, $compile, $rootElement, $document) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.count = 0; $rootScope.$digest(); expect($rootScope.count).toBe(0); // Fire touchstart at 10ms, touchend at 50ms, the click at 300ms. time = 10; browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 50; browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(1); time = 100; browserTrigger(element, 'click',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(1); })); it('should cancel the following click event even when the element has changed', inject( function($rootScope, $compile, $rootElement) { $rootElement.append( '
                      x
                      ' + '
                      y
                      ' ); $compile($rootElement)($rootScope); element1 = $rootElement.find('div').eq(0); element2 = $rootElement.find('div').eq(1); $rootScope.count1 = 0; $rootScope.count2 = 0; $rootScope.$digest(); expect($rootScope.count1).toBe(0); expect($rootScope.count2).toBe(0); time = 10; browserTrigger(element1, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 50; browserTrigger(element1, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count1).toBe(1); time = 100; browserTrigger(element2, 'click',{ keys: [], x: 10, y: 10 }); expect($rootScope.count1).toBe(1); expect($rootScope.count2).toBe(0); })); it('should not cancel clicks on distant elements', inject(function($rootScope, $compile, $rootElement) { $rootElement.append( '
                      x
                      ' + '
                      y
                      ' ); $compile($rootElement)($rootScope); element1 = $rootElement.find('div').eq(0); element2 = $rootElement.find('div').eq(1); $rootScope.count1 = 0; $rootScope.count2 = 0; $rootScope.$digest(); expect($rootScope.count1).toBe(0); expect($rootScope.count2).toBe(0); time = 10; browserTrigger(element1, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 50; browserTrigger(element1, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count1).toBe(1); time = 90; // Verify that it is blured so we don't get soft-keyboard element1[0].blur = jasmine.createSpy('blur'); browserTrigger(element1, 'click',{ keys: [], x: 10, y: 10 }); expect(element1[0].blur).toHaveBeenCalled(); expect($rootScope.count1).toBe(1); time = 100; browserTrigger(element1, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 130; browserTrigger(element1, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count1).toBe(2); // Click on other element that should go through. time = 150; browserTrigger(element2, 'touchstart',{ keys: [], x: 100, y: 120 }); browserTrigger(element2, 'touchend',{ keys: [], x: 100, y: 120 }); browserTrigger(element2, 'click',{ keys: [], x: 100, y: 120 }); expect($rootScope.count2).toBe(1); // Click event for the element that should be busted. time = 200; browserTrigger(element1, 'click',{ keys: [], x: 10, y: 10 }); expect($rootScope.count1).toBe(2); expect($rootScope.count2).toBe(1); })); it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { element1 = $compile('
                      ')($rootScope); $rootScope.count = 0; $rootScope.$digest(); expect($rootScope.count).toBe(0); time = 10; browserTrigger(element1, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 50; browserTrigger(element1, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(1); time = 2700; browserTrigger(element1, 'click',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(2); })); it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { element1 = $compile('
                      ')($rootScope); $rootScope.count = 0; $rootScope.$digest(); expect($rootScope.count).toBe(0); time = 10; browserTrigger(element1, 'touchstart',{ keys: [], x: 10, y: 10 }); time = 50; browserTrigger(element1, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(1); time = 2700; browserTrigger(element1, 'click',{ keys: [], x: 10, y: 10 }); expect($rootScope.count).toBe(2); })); }); describe('click fallback', function() { it('should treat a click as a tap on desktop', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.tapped).toBeFalsy(); browserTrigger(element, 'click'); expect($rootScope.tapped).toEqual(true); })); it('should pass event object', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); browserTrigger(element, 'click'); expect($rootScope.event).toBeDefined(); })); }); describe('disabled state', function() { it('should not trigger click if ngDisabled is true', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.disabled = true; $rootScope.$digest(); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.event).toBeUndefined(); })); it('should trigger click if ngDisabled is false', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.disabled = false; $rootScope.$digest(); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.event).toBeDefined(); })); it('should not trigger click if regular disabled is true', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.event).toBeUndefined(); })); it('should not trigger click if regular disabled is present', inject(function($rootScope, $compile) { element = $compile('')($rootScope); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.event).toBeUndefined(); })); it('should trigger click if regular disabled is not present', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect($rootScope.event).toBeDefined(); })); }); describe('the normal click event', function() { it('should be capturable by other handlers', inject(function($rootScope, $compile) { var called = false; element = $compile('
                      ')($rootScope); element.on('click', function() { called = true; }); browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); expect(called).toEqual(true); })); }); }); angular.js-1.2.11/test/ngTouch/directive/ngSwipeSpec.js000066400000000000000000000143031227375216300230020ustar00rootroot00000000000000'use strict'; // Wrapper to abstract over using touch events or mouse events. var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, endEvent) { describe('ngSwipe with ' + description + ' events', function() { var element; if (restrictBrowsers) { // TODO(braden): Once we have other touch-friendly browsers on CI, allow them here. // Currently Firefox and IE refuse to fire touch events. var chrome = /chrome/.test(navigator.userAgent.toLowerCase()); if (!chrome) { return; } } // Skip tests on IE < 9. These versions of IE don't support createEvent(), and so // we cannot control the (x,y) position of events. // It works fine in IE 8 under manual testing. var msie = +((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]); if (msie < 9) { return; } beforeEach(function() { module('ngTouch'); }); afterEach(function() { dealoc(element); }); it('should swipe to the left', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent, { keys : [], x : 100, y : 20 }); browserTrigger(element, endEvent,{ keys: [], x: 20, y: 20 }); expect($rootScope.swiped).toBe(true); })); it('should swipe to the right', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent,{ keys: [], x: 20, y: 20 }); browserTrigger(element, endEvent,{ keys: [], x: 90, y: 20 }); expect($rootScope.swiped).toBe(true); })); it('should pass event object', inject(function($rootScope, $compile) { element = $compile('
                      ')($rootScope); $rootScope.$digest(); browserTrigger(element, startEvent, { keys : [], x : 100, y : 20 }); browserTrigger(element, endEvent,{ keys: [], x: 20, y: 20 }); expect($rootScope.event).toBeDefined(); })); it('should not swipe if you move too far vertically', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent,{ keys: [], x: 90, y: 20 }); browserTrigger(element, moveEvent,{ keys: [], x: 70, y: 200 }); browserTrigger(element, endEvent,{ keys: [], x: 20, y: 20 }); expect($rootScope.swiped).toBeUndefined(); })); it('should not swipe if you slide only a short distance', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent,{ keys: [], x: 90, y: 20 }); browserTrigger(element, endEvent,{ keys: [], x: 80, y: 20 }); expect($rootScope.swiped).toBeUndefined(); })); it('should not swipe if the swipe leaves the element', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent,{ keys: [], x: 20, y: 20 }); browserTrigger(element, moveEvent,{ keys: [], x: 40, y: 20 }); expect($rootScope.swiped).toBeUndefined(); })); it('should not swipe if the swipe starts outside the element', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, moveEvent,{ keys: [], x: 10, y: 20 }); browserTrigger(element, endEvent,{ keys: [], x: 90, y: 20 }); expect($rootScope.swiped).toBeUndefined(); })); it('should emit "swipeleft" events for left swipes', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); var eventFired = false; element.on('swipeleft', function() { eventFired = true; }); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 20 }); browserTrigger(element, endEvent,{ keys: [], x: 20, y: 20 }); expect(eventFired).toEqual(true); })); it('should emit "swiperight" events for right swipes', inject(function($rootScope, $compile, $rootElement) { element = $compile('
                      ')($rootScope); $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); var eventFired = false; element.on('swiperight', function() { eventFired = true; }); browserTrigger(element, startEvent,{ keys: [], x: 20, y: 20 }); browserTrigger(element, endEvent,{ keys: [], x: 100, y: 20 }); expect(eventFired).toEqual(true); })); }); } swipeTests('touch', true /* restrictBrowers */, 'touchstart', 'touchmove', 'touchend'); swipeTests('mouse', false /* restrictBrowers */, 'mousedown', 'mousemove', 'mouseup'); angular.js-1.2.11/test/ngTouch/swipeSpec.js000066400000000000000000000255701227375216300205470ustar00rootroot00000000000000'use strict'; // Wrapper to abstract over using touch events or mouse events. var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, endEvent) { describe('$swipe with ' + description + ' events', function() { var element; if (restrictBrowsers) { // TODO(braden): Once we have other touch-friendly browsers on CI, allow them here. // Currently Firefox and IE refuse to fire touch events. var chrome = /chrome/.test(navigator.userAgent.toLowerCase()); if (!chrome) { return; } } // Skip tests on IE < 9. These versions of IE don't support createEvent(), and so // we cannot control the (x,y) position of events. // It works fine in IE 8 under manual testing. var msie = +((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]); if (msie < 9) { return; } beforeEach(function() { module('ngTouch'); }); afterEach(function() { dealoc(element); }); it('should trigger the "start" event', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 20 }); expect(events.start).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); })); it('should trigger the "move" event after a "start"', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 20 }); expect(events.start).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, moveEvent,{ keys: [], x: 140, y: 20 }); expect(events.start).toHaveBeenCalled(); expect(events.move).toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); })); it('should not trigger a "move" without a "start"', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, moveEvent,{ keys: [], x: 100, y: 40 }); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); })); it('should not trigger an "end" without a "start"', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, endEvent,{ keys: [], x: 100, y: 40 }); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); })); it('should trigger a "start", many "move"s and an "end"', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, moveEvent,{ keys: [], x: 120, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 130, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 140, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 150, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 160, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 170, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 180, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move.calls.length).toBe(7); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, endEvent,{ keys: [], x: 200, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move.calls.length).toBe(7); expect(events.end).toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); })); it('should not start sending "move"s until enough horizontal motion is accumulated', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, moveEvent,{ keys: [], x: 101, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 105, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 110, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 115, y: 40 }); browserTrigger(element, moveEvent,{ keys: [], x: 120, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move.calls.length).toBe(3); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, endEvent,{ keys: [], x: 200, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move.calls.length).toBe(3); expect(events.end).toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); })); it('should stop sending anything after vertical motion dominates', inject(function($rootScope, $swipe, $compile) { element = $compile('
                      ')($rootScope); var events = { start: jasmine.createSpy('startSpy'), move: jasmine.createSpy('moveSpy'), cancel: jasmine.createSpy('cancelSpy'), end: jasmine.createSpy('endSpy') }; $swipe.bind(element, events); expect(events.start).not.toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, startEvent,{ keys: [], x: 100, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.cancel).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, moveEvent,{ keys: [], x: 101, y: 41 }); browserTrigger(element, moveEvent,{ keys: [], x: 105, y: 55 }); browserTrigger(element, moveEvent,{ keys: [], x: 110, y: 60 }); browserTrigger(element, moveEvent,{ keys: [], x: 115, y: 70 }); browserTrigger(element, moveEvent,{ keys: [], x: 120, y: 80 }); expect(events.start).toHaveBeenCalled(); expect(events.cancel).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); browserTrigger(element, endEvent,{ keys: [], x: 200, y: 40 }); expect(events.start).toHaveBeenCalled(); expect(events.cancel).toHaveBeenCalled(); expect(events.move).not.toHaveBeenCalled(); expect(events.end).not.toHaveBeenCalled(); })); }); } swipeTests('touch', true /* restrictBrowers */, 'touchstart', 'touchmove', 'touchend'); swipeTests('mouse', false /* restrictBrowers */, 'mousedown', 'mousemove', 'mouseup'); angular.js-1.2.11/validate-commit-msg.js000077500000000000000000000051441227375216300200600ustar00rootroot00000000000000#!/usr/bin/env node /** * Git COMMIT-MSG hook for validating commit message * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit * * Installation: * >> cd * >> ln -s ../../validate-commit-msg.js .git/hooks/commit-msg */ var fs = require('fs'); var util = require('util'); var MAX_LENGTH = 100; var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/; var IGNORED = /^WIP\:/; var TYPES = { feat: true, fix: true, docs: true, style: true, refactor: true, perf: true, test: true, chore: true, revert: true }; var error = function() { // gitx does not display it // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812 console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments)); }; var validateMessage = function(message) { var isValid = true; if (IGNORED.test(message)) { console.log('Commit message validation ignored.'); return true; } if (message.length > MAX_LENGTH) { error('is longer than %d characters !', MAX_LENGTH); isValid = false; } var match = PATTERN.exec(message); if (!match) { error('does not match "(): " ! was: ' + message); return false; } var type = match[1]; var scope = match[3]; var subject = match[4]; if (!TYPES.hasOwnProperty(type)) { error('"%s" is not allowed type !', type); return false; } // Some more ideas, do want anything like this ? // - allow only specific scopes (eg. fix(docs) should not be allowed ? // - auto correct the type to lower case ? // - auto correct first letter of the subject to lower case ? // - auto add empty line after subject ? // - auto remove empty () ? // - auto correct typos in type ? // - store incorrect messages, so that we can learn return isValid; }; var firstLineFromBuffer = function(buffer) { return buffer.toString().split('\n').shift(); }; // publish for testing exports.validateMessage = validateMessage; // hacky start if not run by jasmine :-D if (process.argv.join('').indexOf('jasmine-node') === -1) { var commitMsgFile = process.argv[2]; var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs'); fs.readFile(commitMsgFile, function(err, buffer) { var msg = firstLineFromBuffer(buffer); if (!validateMessage(msg)) { fs.appendFile(incorrectLogFile, msg + '\n', function() { process.exit(1); }); } else { process.exit(0); } }); } angular.js-1.2.11/validate-commit-msg.spec.js000066400000000000000000000053411227375216300210050ustar00rootroot00000000000000describe('validate-commit-msg.js', function() { var m = require('./validate-commit-msg'); var errors = []; var logs = []; var VALID = true; var INVALID = false; beforeEach(function() { errors.length = 0; logs.length = 0; spyOn(console, 'error').andCallFake(function(msg) { errors.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor }); spyOn(console, 'log').andCallFake(function(msg) { logs.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor }); }); describe('validateMessage', function() { it('should be valid', function() { expect(m.validateMessage('fixup! fix($compile): something')).toBe(VALID); expect(m.validateMessage('fix($compile): something')).toBe(VALID); expect(m.validateMessage('feat($location): something')).toBe(VALID); expect(m.validateMessage('docs($filter): something')).toBe(VALID); expect(m.validateMessage('style($http): something')).toBe(VALID); expect(m.validateMessage('refactor($httpBackend): something')).toBe(VALID); expect(m.validateMessage('test($resource): something')).toBe(VALID); expect(m.validateMessage('chore($controller): something')).toBe(VALID); expect(m.validateMessage('chore(foo-bar): something')).toBe(VALID); expect(m.validateMessage('chore(*): something')).toBe(VALID); expect(m.validateMessage('chore(guide/location): something')).toBe(VALID); expect(m.validateMessage('revert(foo): something')).toBe(VALID); expect(errors).toEqual([]); }); it('should validate 100 characters length', function() { var msg = "fix($compile): something super mega extra giga tera long, maybe even longer and longer and longer... "; expect(m.validateMessage(msg)).toBe(INVALID); expect(errors).toEqual(['INVALID COMMIT MSG: is longer than 100 characters !']); }); it('should validate "(): " format', function() { var msg = 'not correct format'; expect(m.validateMessage(msg)).toBe(INVALID); expect(errors).toEqual(['INVALID COMMIT MSG: does not match "(): " ! was: not correct format']); }); it('should validate type', function() { expect(m.validateMessage('weird($filter): something')).toBe(INVALID); expect(errors).toEqual(['INVALID COMMIT MSG: "weird" is not allowed type !']); }); it('should allow empty scope', function() { expect(m.validateMessage('fix: blablabla')).toBe(VALID); }); it('should allow dot in scope', function() { expect(m.validateMessage('chore(mocks.$httpBackend): something')).toBe(VALID); }); it('should ignore msg prefixed with "WIP: "', function() { expect(m.validateMessage('WIP: bullshit')).toBe(VALID); }); }); }); angular.js-1.2.11/watchr-docs.rb000066400000000000000000000003371227375216300164160ustar00rootroot00000000000000# config file for watchr http://github.com/mynyml/watchr # install: gem install watchr # run: watch watchr-docs.rb watch( '^src/|^docs/' ) do system 'echo "\n\ndoc run started @ `date`"; node docs/src/gen-docs.js' end