Compare commits

..

41 Commits

Author SHA1 Message Date
Igor Minar 5f0af2cd0e cutting the 0.9.3 cold-resistance release 2010-11-10 22:15:16 -08:00
Igor Minar 7411b24812 updating the release notes for 0.9.3 release 2010-11-10 22:14:16 -08:00
Igor Minar ae5f6f48b4 scenario repeater should match only visible stuff 2010-11-10 21:04:44 -08:00
Igor Minar c5b2bf083c Make ng:repeat expose $position.
- $position is a textual representation of the position of
  repeated item ('first', 'middle', 'last')
- added specs for $index
2010-11-10 21:04:44 -08:00
Misko Hevery 0499c47270 added ng:switch-when-default; changed $watch to always fire on init. (may be backward incompatible) 2010-11-10 21:01:17 -08:00
Misko Hevery 43a4ff4cdf Example snippets now have full html snippets 2010-11-10 21:01:05 -08:00
Misko Hevery 6b8ed42670 Added Directives 2010-11-10 12:02:49 -08:00
Elliott Sprehn c57df3dc77 Make the docs look a little nicer 2010-11-10 11:01:30 -08:00
Igor Minar 6d53808475 improve jsdocs
- improve json filter example
- improve filter overview doc
- improving validator overview jsdocs
- simplify number filter examples and make them live + add specs
- various doc fixes
2010-11-09 22:33:27 -08:00
Misko Hevery a7e8a503fd proper trimming of leading whitespace; url restriction to angular.* 2010-11-09 22:27:37 -08:00
Igor Minar 324694a58b Better example widget
- syntax highlighting
- tabless design
- rename widget to doc:example
- rename widget files (wiki_widget.* -> doc_widget.*)
- example section is now optional
2010-11-09 22:27:31 -08:00
Igor Minar effcd340e9 updating watchr-docs.rb 2010-11-09 21:44:51 -08:00
Igor Minar 264f960800 added spec for auto bootstrap with #autobind 2010-11-09 19:58:42 -08:00
Elliott Sprehn 257e97a65f Support substring matching of bindings with repeater(). Closes #123 2010-11-09 13:39:12 -08:00
Misko Hevery c048f0d8e8 Added formatter documentation. 2010-11-09 09:15:29 -08:00
Elliott Sprehn 96e37a0866 Add key/value jQuery methods in a loop. Support 'css' method.
Closes #134
2010-11-09 08:02:49 -08:00
Vojta Jina 5062d32621 Updated parser tests to use jasmine expects
And some formatting as well and a bit of rafactor...
2010-11-09 07:38:03 -08:00
Vojta Jina d458f31711 Updated filters tests to use jasmine expects
And some small formatting...
2010-11-09 07:38:03 -08:00
Igor Minar fc9ce9ec07 make angular.String.toDate consider all time fractions as optional 2010-11-08 22:49:30 -08:00
Igor Minar da17c61444 angular.Date.toString should use toISOString if available 2010-11-08 22:49:30 -08:00
Igor Minar e5c135ac50 Support ISO 8601 extended datetime format troughout angular.
Support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
  in EcmaScript 5 throughout angular. This means that the following apis switched from
  YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
  - angular.Date.toString
  - angular.String.toDate
  - JSON serialization and deserialization (used by json filter, $xhr and $resource)
2010-11-08 22:49:30 -08:00
Igor Minar 1a43f36e23 Add isDate method + fix old code 2010-11-08 22:49:29 -08:00
Igor Minar 1c305dc67a fixing spec description for the date filter 2010-11-08 22:46:41 -08:00
Igor Minar a397645537 date filter should accept ISO 8601 formatted string as input
Closes #125
2010-11-08 22:46:41 -08:00
Igor Minar f077649f48 TzDate should support various UTC methods 2010-11-08 22:46:41 -08:00
Vojta Jina f3ac2cd434 Update AngularSpec tests to use BDD style - jasmine expect 2010-11-07 14:42:04 -08:00
Vojta Jina 7779630989 Added tests for angular.service
- should allow to override a service
- should preserve angular properties on override
- should not preserve non-angular properties on override
2010-11-07 14:42:03 -08:00
Vojta Jina 00ca67e4be Issue #51: Update extensionMap()
If user override existing extension, angular properties ($) will be preserved.

This piece of logic could be refactored into separate method:
Something like we have extend(), addMissingProperties() - I can't find a name
for this method...

Closes #51
2010-11-07 14:42:03 -08:00
Misko Hevery 91b6c5f7ff Added documentation for validators.
BACKWARD INCOMPATIBLE: removed ssn validators, since it is unlikely that most people will need it and if they do, they can added it thorough RegExp
2010-11-07 13:06:55 -08:00
Vojta Jina 5be325a0c1 Refactored toJsonArray(), added isBoolean() function 2010-11-05 21:39:00 -07:00
Vojta Jina b7027b9d87 Updated toJson() to not serialize window/document objects.
The reason to void these to objects is that they cause all sorts
of problems like exceptions being thrown and infinite loops occuring
when we iterate over object properties.
2010-11-05 21:39:00 -07:00
Vojta Jina fe8353bc5e Changed toJson() to not ignore $ properties 2010-11-05 21:39:00 -07:00
Vojta Jina c780030c6e Json - corrected test descriptions and changed to BDD style expect() 2010-11-05 21:39:00 -07:00
Misko Hevery d5e9f38f3d fix error in json parser which did not allow 1.2E10 as number 2010-11-05 16:41:36 -07:00
Misko Hevery dc66687149 added tests for documentation 2010-11-05 15:05:24 -07:00
Misko Hevery 3d6a099d6e changed to showdown from markup. added validator overview 2010-11-05 13:32:37 -07:00
Misko Hevery 8767e766d1 eclipse settings for auto run of docs 2010-11-04 14:25:11 -07:00
Misko Hevery 47066e70e1 added documentation for ng:include and ng:widget and test for doc collector. 2010-11-04 14:24:31 -07:00
Misko Hevery c0d30aedfc added LICENSE to IDEA 2010-11-04 11:07:49 -07:00
Misko Hevery b246d6e2ab rename Index.js to index.js 2010-11-04 11:02:45 -07:00
Igor Minar 3b04b48b7c preparations for the 0.9.3 cold-resistance iteration 2010-11-03 13:09:52 -07:00
54 changed files with 4462 additions and 2749 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1262905463390_2&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/test&quot; type=&quot;2&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js}/test.sh"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
+3 -3
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/build&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs/callback.js&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/docs/collect.js&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/docs/filter.template&quot; type=&quot;1&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+1
View File
@@ -3,3 +3,4 @@ angularjs.netrc
jstd.log
.DS_Store
regression/temp.html
.idea/workspace.xml
Generated
+67
View File
@@ -0,0 +1,67 @@
IMPORTANT: THIS IS TO CERTIFY THE RIGHT TO USE THE JETBRAINS SOFTWARE PRODUCT, GRANTED BY JETBRAINS S.R.O. UNDER THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT INCLUDED WITH THE SOFTWARE. PLEASE SAVE A COPY OF THIS EMAIL FOR FUTURE REFERENCES.
========LICENSE DETAILS========
Type: Open Source License
Reference No*: LC-93161-D352729080
Date of Issue: 3 November 2010
Expiration Date: 3 November 2011
Number of Authorized Users: not limited
* Please quote this reference when contacting JetBrains
===========LICENSEE============
Name: angularjs
Customer ID: 93161
Address:
=======SOFTWARE PRODUCT========
Product Name: WebStorm
Licensed Version: 1.0 and any new product release which is made generally available before 3 November 2011
The software is shipped electronically and is available for download from:
http://www.jetbrains.com/webstorm/download/
Your WebStorm license includes a 1-year upgrade subscription qualifying you for free upgrades to any new versions of WebStorm released by JetBrains during your upgrade subscription period starting on your license issue date and ending on 3 November 2011. To check availability of the new versions of WebStorm, please visit http://www.jetbrains.com.
For running any new version of WebStorm released by JetBrains during your upgrade subscription period, please use the included below licensing details.
You can renew your upgrade subscription before or after its expiration. Your new subscription period will start on the date following the expiration date of your current upgrade subscription.
=========INSTALLATION==========
Run WebStorm and follow the Installation Wizard's instructions. To register for use of the software or change your existing registration details, go to Help/Register menu of the software and enter the included below the User Name and License Key(s) into the registration dialog:
User Name: angularjs
===== LICENSE BEGIN =====
93161-03112010
00000jBsEx59XVlc79fV"aAqWXQ09e
jQsg5TNp5X4HGhc10LNBdu!!ejRcFG
7h3S6T09YcRWs23TH0RgaM87!HqmQo
===== LICENSE END =====
===DOCUMENTATION AND SUPPORT===
WebStorm documentation:
http://www.jetbrains.com/webstorm/documentation/
Available support resources:
http://www.jetbrains.com/support/
Technical support contact:
support@jetbrains.com
Contact for the license renewal requests:
opensource@jetbrains.com
For questions, please contact:
sales@jetbrains.com
JetBrains Sales Team
http://www.jetbrains.com
"Develop with pleasure!"
-500
View File
@@ -1,500 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" readonly="true" id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="updated Rakefile to support packaging of the docs">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/filters.js" afterPath="$PROJECT_DIR$/src/filters.js" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/docs/docs-scenario.html" afterPath="$PROJECT_DIR$/docs/docs-scenario.html" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
</list>
<ignored path=".idea/workspace.xml" />
<ignored path="angular.js.iws" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value="" />
<option name="REVERSE_PATCH" value="false" />
</component>
<component name="DaemonCodeAnalyzer">
<disable_hints />
</component>
<component name="FavoritesManager">
<favorites_list name="angular.js" />
</component>
<component name="FileColors" enabled="true" enabledForTabs="true" />
<component name="FileEditorManager">
<splitter split-orientation="horizontal" split-proportion="0.5">
<split-first>
<leaf>
<file leaf-file-name="filters.js" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/filters.js">
<provider selected="true" editor-type-id="text-editor">
<state line="377" column="5" selection-start="12137" selection-end="12137" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="docs-scenario.html" pinned="false" current="true" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/docs/docs-scenario.html">
<provider selected="true" editor-type-id="text-editor">
<state line="3" column="26" selection-start="119" selection-end="119" vertical-scroll-proportion="0.039173014">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="validators.js" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/validators.js">
<provider selected="true" editor-type-id="text-editor">
<state line="54" column="69" selection-start="1564" selection-end="1611" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="test.sh" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test.sh">
<provider selected="true" editor-type-id="text-editor">
<state line="3" column="13" selection-start="59" selection-end="59" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="docs-scenario.html" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/build/docs/docs-scenario.html">
<provider selected="true" editor-type-id="text-editor">
<state line="5" column="44" selection-start="259" selection-end="259" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="index.html" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/build/docs/index.html">
<provider selected="true" editor-type-id="text-editor">
<state line="24" column="7" selection-start="1033" selection-end="1033" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="angular.filter.html.html" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/build/docs/angular.filter.html.html">
<provider selected="true" editor-type-id="text-editor">
<state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
</leaf>
</split-first>
<split-second>
<leaf>
<file leaf-file-name="FiltersSpec.js" pinned="false" current="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/test/FiltersSpec.js">
<provider selected="true" editor-type-id="text-editor">
<state line="89" column="20" selection-start="2996" selection-end="2996" vertical-scroll-proportion="0.7635783">
<folding />
</state>
</provider>
</entry>
</file>
</leaf>
</split-second>
</splitter>
</component>
<component name="FindManager">
<FindUsagesManager>
<setting name="OPEN_NEW_TAB" value="false" />
</FindUsagesManager>
</component>
<component name="Git.Settings">
<option name="GIT_EXECUTABLE" value="/usr/local/git/bin/git" />
<option name="CHECKOUT_INCLUDE_TAGS" value="false" />
</component>
<component name="IdeDocumentHistory">
<option name="changedFiles">
<list>
<option value="$PROJECT_DIR$/lib/nodeserver/server.js" />
<option value="$PROJECT_DIR$/Rakefile" />
<option value="$PROJECT_DIR$/docs/collect.js" />
<option value="$PROJECT_DIR$/src/scenario/dsl.js" />
<option value="$PROJECT_DIR$/test.sh" />
<option value="$PROJECT_DIR$/test/FiltersSpec.js" />
<option value="$PROJECT_DIR$/src/filters.js" />
<option value="$PROJECT_DIR$/docs/docs-scenario.html" />
</list>
</option>
</component>
<component name="ProjectLevelVcsManager">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectReloadState">
<option name="STATE" value="0" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="docs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="build" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="build" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="pkg" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular-0.9.2-a838b3ef" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="build" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="docs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
</subPane>
</pane>
<pane id="Favorites" />
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="options.splitter.main.proportions" value="0.3" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="recentsLimit" value="5" />
<property name="options.lastSelected" value="project.propVCSSupport.VCSs.Git" />
<property name="GoToClass.includeJavaFiles" value="false" />
<property name="options.splitter.details.proportions" value="0.2" />
<property name="options.searchVisible" value="true" />
</component>
<component name="RunManager" selected="Bash.gen_docs">
<configuration default="false" name="gen_docs.sh" type="BashConfigurationType" factoryName="Bash" temporary="true">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" />
<option name="PARAMETERS" value="" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
<configuration default="false" name="test.sh" type="BashConfigurationType" factoryName="Bash" temporary="true">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test.sh" />
<option name="PARAMETERS" value="" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
<configuration default="true" type="BashConfigurationType" factoryName="Bash">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="" />
<option name="PARAMETERS" value="" />
<method />
</configuration>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="Bash.gen_docs.sh" />
<item index="1" class="java.lang.String" itemvalue="Bash.rake compile" />
<item index="2" class="java.lang.String" itemvalue="Bash.gen_docs" />
<item index="3" class="java.lang.String" itemvalue="Bash.test.sh" />
</list>
</component>
<component name="ShelveChangesManager" show_recycled="false" />
<component name="SvnConfiguration" maxAnnotateRevisions="500">
<option name="USER" value="" />
<option name="PASSWORD" value="" />
<option name="LAST_MERGED_REVISION" />
<option name="UPDATE_RUN_STATUS" value="false" />
<option name="MERGE_DRY_RUN" value="false" />
<option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
<option name="UPDATE_LOCK_ON_DEMAND" value="false" />
<option name="IGNORE_SPACES_IN_MERGE" value="false" />
<option name="DETECT_NESTED_COPIES" value="true" />
<option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
<option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
<option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
<configuration useDefault="true">$PROJECT_DIR$/../../.subversion_IDEA</configuration>
<myIsUseDefaultProxy>false</myIsUseDefaultProxy>
<supportedVersion>125</supportedVersion>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="" />
<created>1288738700234</created>
<updated>1288738700234</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="0" y="22" width="2560" height="1574" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32943603" sideWeight="0.0" order="7" side_tool="false" content_ui="tabs" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
<window_info id="Dependency Viewer" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.18020505" sideWeight="0.66574967" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32943603" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="VcsManagerConfiguration">
<option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
<option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
<option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
<option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
<option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
<option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
<option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
<option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
<option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
<option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
<option name="FORCE_NON_EMPTY_COMMENT" value="false" />
<option name="LAST_COMMIT_MESSAGE" value="updated Rakefile to support packaging of the docs" />
<option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
<option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
<option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
<option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
<option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
<option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
<option name="ACTIVE_VCS_NAME" />
<option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
<option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
<option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
<option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
<MESSAGE value="move the output of generation to build" />
<MESSAGE value="updated Rakefile to support packaging of the docs" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="angular.js" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
</PATH>
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/version.yaml">
<provider selected="true" editor-type-id="text-editor">
<state line="2" column="23" selection-start="50" selection-end="58" vertical-scroll-proportion="0.025559105" />
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/docs/collect.js">
<provider selected="true" editor-type-id="text-editor">
<state line="78" column="0" selection-start="2539" selection-end="2539" vertical-scroll-proportion="0.99680513">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/Rakefile">
<provider selected="true" editor-type-id="text-editor">
<state line="184" column="5" selection-start="5171" selection-end="5171" vertical-scroll-proportion="0.47603834">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/Resource.js">
<provider selected="true" editor-type-id="text-editor">
<state line="37" column="1" selection-start="1038" selection-end="1038" vertical-scroll-proportion="0.47284344">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/ResourceSpec.js">
<provider selected="true" editor-type-id="text-editor">
<state line="13" column="35" selection-start="399" selection-end="399" vertical-scroll-proportion="0.16613418" />
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/nodeserver/server.js">
<provider selected="true" editor-type-id="text-editor">
<state line="37" column="0" selection-start="864" selection-end="864" vertical-scroll-proportion="0.4345048">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/scenario/dsl.js">
<provider selected="true" editor-type-id="text-editor">
<state line="109" column="11" selection-start="3010" selection-end="3010" vertical-scroll-proportion="-0.021905806">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/validators.js">
<provider selected="true" editor-type-id="text-editor">
<state line="54" column="69" selection-start="1564" selection-end="1611" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test.sh">
<provider selected="true" editor-type-id="text-editor">
<state line="3" column="13" selection-start="59" selection-end="59" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/build/docs/docs-scenario.html">
<provider selected="true" editor-type-id="text-editor">
<state line="5" column="44" selection-start="259" selection-end="259" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/build/docs/index.html">
<provider selected="true" editor-type-id="text-editor">
<state line="24" column="7" selection-start="1033" selection-end="1033" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/build/docs/angular.filter.html.html">
<provider selected="true" editor-type-id="text-editor">
<state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/FiltersSpec.js">
<provider selected="true" editor-type-id="text-editor">
<state line="89" column="20" selection-start="2996" selection-end="2996" vertical-scroll-proportion="0.7635783">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/filters.js">
<provider selected="true" editor-type-id="text-editor">
<state line="377" column="5" selection-start="12137" selection-end="12137" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/docs/docs-scenario.html">
<provider selected="true" editor-type-id="text-editor">
<state line="3" column="26" selection-start="119" selection-end="119" vertical-scroll-proportion="0.039173014">
<folding />
</state>
</provider>
</entry>
</component>
</project>
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="test/" kind="src" path="src"/>
<classpathentry kind="src" path="docs"/>
<classpathentry kind="src" path="src/test"/>
<classpathentry excluding="docs-data.js|docs-scenario.js" kind="src" path="docs"/>
<classpathentry excluding="test/" kind="src" path="test"/>
<classpathentry kind="src" path="test/test"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
+27
View File
@@ -1,3 +1,30 @@
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
### Docs
- prettier docs app with syntax highlighting for examples, etc
- added documentation, examples and scenario tests for many more apis including:
- all directives
- all formatters
- all validators
- some widgets
### Api
- date filter now accepts strings that angular.String.toDate can convert to Date objects
- angular.String.toDate supports ISO8061 formated strings with all time fractions being optional
- ng:repeat now exposes $position with values set to 'first', 'middle' or 'last'
- ng:switch now supports ng:switch-default as fallback switch option
### Breaking changes
- we now support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
in EcmaScript 5 throughout angular. This means that the following apis switched from
YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
- angular.Date.toString
- angular.String.fromDate
- JSON serialization and deserialization (used by json filter, $xhr and $resource)
- removed SSN validator. It's unlikely that most people will need it and if they do, it can be added
simple RegExp validator.
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
### Docs
+73 -17
View File
@@ -1,8 +1,10 @@
require.paths.push("./lib");
require.paths.push(__dirname);
var fs = require('fs'),
spawn = require('child_process').spawn,
mustache = require('../lib/mustache'),
callback = require('./callback'),
markdown = require('../lib/markdown');
mustache = require('mustache'),
callback = require('callback'),
Showdown = require('showdown').Showdown;
var documentation = {
section:{},
@@ -11,6 +13,7 @@ var documentation = {
var SRC_DIR = "docs/";
var OUTPUT_DIR = "build/docs/";
var NEW_LINE = /\n\r?/;
var work = callback.chain(function () {
console.log('Parsing Angular Reference Documentation');
@@ -40,11 +43,11 @@ var work = callback.chain(function () {
copy('docs-scenario.html', callback.chain());
copy('index.html', callback.chain());
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
mergeTemplate('wiki_widgets.css', 'wiki_widgets.css', documentation, callback.chain());
mergeTemplate('wiki_widgets.js', 'wiki_widgets.js', documentation, callback.chain());
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
console.log('DONE');
});
work();
if (!this.testmode) work();
////////////////////
function noop(){}
@@ -78,6 +81,37 @@ function mergeTemplate(template, output, doc, callback){
}
function trim(text) {
var MAX = 9999;
var empty = RegExp.prototype.test.bind(/^\s*$/);
var lines = text.split('\n');
var minIndent = MAX;
lines.forEach(function(line){
minIndent = Math.min(minIndent, indent(line));
});
for ( var i = 0; i < lines.length; i++) {
lines[i] = lines[i].substring(minIndent);
}
// remove leading lines
while (empty(lines[0])) {
lines.shift();
}
// remove trailing
while (empty(lines[lines.length - 1])) {
lines.pop();
}
return lines.join('\n');
function indent(line) {
for(var i = 0; i < line.length; i++) {
if (line.charAt(i) != ' ') {
return i;
}
}
return MAX;
}
}
function unknownTag(doc, name) {
var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name;
console.log(error);
@@ -93,7 +127,21 @@ function escapedHtmlTag(doc, name, value) {
}
function markdownTag(doc, name, value) {
doc[name] = markdown.toHTML(value);
doc[name] = markdown(value.replace(/^#/gm, '##')).
replace(/\<pre\>/gmi, '<pre class="brush: xml; brush: js;" ng:non-bindable>');
}
function markdown(text) {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
return new Showdown.converter().makeHtml(text);
}
function markdownNoP(text) {
var lines = markdown(text).split(NEW_LINE);
var last = lines.length - 1;
lines[0] = lines[0].replace(/^<p>/, '');
lines[last] = lines[last].replace(/<\/p>$/, '');
return lines.join('\n');
}
var TAG = {
@@ -103,23 +151,30 @@ var TAG = {
namespace: valueTag,
css: valueTag,
see: valueTag,
usageContent: valueTag,
'function': valueTag,
description: markdownTag,
TODO: markdownTag,
returns: markdownTag,
paramDescription: markdownTag,
exampleDescription: markdownTag,
element: valueTag,
name: function(doc, name, value) {
doc.name = value;
doc.shortName = value.split(/\./).pop();
var match = value.match(/^angular[\.\#](([^\.]+)\.(.*)|(.*))/);
doc.shortName = match[3] || match[4];
},
param: function(doc, name, value){
doc.param = doc.param || [];
doc.paramRest = doc.paramRest || [];
var match = value.match(/^({([^\s=]+)(=)?}\s*)?([^\s]+|\[(\S+)+=([^\]]+)\])\s+(.*)/);
var match = value.match(/^({([^\s=]+)(=)?}\s*)?(([^\s=]+)|\[(\S+)+=([^\]]+)\])\s+(.*)/);
if (match) {
var param = {
type: match[2],
name: match[4] || match[5],
'default':match[6],
description:match[7]};
name: match[6] || match[5],
'default':match[7],
description:markdownNoP(value.replace(match[0], match[8]))
};
doc.param.push(param);
if (!doc.paramFirst) {
doc.paramFirst = param;
@@ -137,12 +192,12 @@ function parseNgDoc(doc){
var atName;
var atText;
var match;
doc.raw.text.split(/\n/).forEach(function(line, lineNumber){
if (match = line.match(/^@(\w+)(\s+(.*))?/)) {
doc.raw.text.split(NEW_LINE).forEach(function(line, lineNumber){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ...
// if we have existing name
if (atName) {
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
(TAG[atName] || unknownTag)(doc, atName, trim(atText.join('\n')));
}
atName = match[1];
atText = [];
@@ -162,7 +217,7 @@ function parseNgDoc(doc){
function findNgDoc(file, callback) {
fs.readFile(file, callback.waitFor(function(err, content){
var lines = content.toString().split(/\n\r?/);
var lines = content.toString().split(NEW_LINE);
var doc;
var match;
var inDoc = false;
@@ -178,8 +233,9 @@ function findNgDoc(file, callback) {
if (inDoc && line.match(/\*\//)) {
doc.raw.text = doc.raw.text.join('\n');
doc.raw.text = doc.raw.text.replace(/^\n/, '');
if (doc.raw.text.match(/@ngdoc/))
if (doc.raw.text.match(/@ngdoc/)){
callback(doc);
}
doc = null;
inDoc = false;
}
+37
View File
@@ -0,0 +1,37 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
<pre>
&lt;{{element}} {{shortName}}="{{paramFirst.name}}"&gt;
...
&lt;/{{element}}&gt;
</pre>
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
{{#css}}<h3>CSS</h3>{{/css}}
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+35
View File
@@ -0,0 +1,35 @@
@namespace doc url("http://docs.angularjs.org/");
doc\:example {
display: none;
}
ul.doc-example {
list-style-type: none;
position: relative;
font-size: 14px;
}
ul.doc-example > li {
border: 2px solid gray;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
background-color: white;
margin-bottom: 20px;
}
ul.doc-example > li.doc-example-heading {
border: none;
border-radius: none;
margin-bottom: -10px;
}
li.doc-example-live {
padding: 10px;
font-size: 1.2em;
}
div.syntaxhighlighter {
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
}
+75
View File
@@ -0,0 +1,75 @@
(function(){
var angularJsUrl;
var scripts = document.getElementsByTagName("script");
var filename = /(.*\/)angular([^\/]*)/;
for(var j = 0; j < scripts.length; j++) {
var src = scripts[j].src;
if (src && src.match(filename)) {
angularJsUrl = src;
}
}
var HTML_TEMPLATE =
'<!doctype html>\n' +
'<html xmlns:ng="http://angularjs.org">\n' +
' <script type="text/javascript" ng:autobind\n' +
' src="' + angularJsUrl + '"></script>\n' +
' <body>\n' +
'_HTML_SOURCE_\n' +
' </body>\n' +
'</html>';
angular.widget('doc:example', function(element){
this.descend(true); //compile the example code
element.hide();
var example = element.find('doc\\:source').eq(0),
exampleSrc = example.text(),
scenario = element.find('doc\\:scenario').eq(0);
var code = indent(exampleSrc);
var tabs = angular.element(
'<ul class="doc-example">' +
'<li class="doc-example-heading"><h3>Source</h3></li>' +
'<li class="doc-example-source" ng:non-bindable>' +
'<pre class="brush: js; html-script: true; highlight: [' +
code.hilite + ']; toolbar: false;"></pre></li>' +
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
'<li class="doc-example-live">' + exampleSrc +'</li>' +
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' +
'</ul>');
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
element.html('');
element.append(tabs);
element.show();
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
try {
eval(script);
} catch (e) {
alert(e);
}
return function() {
SyntaxHighlighter.highlight();
};
});
function indent(text) {
var lines = text.split(/\n/);
var lineNo = [];
while (lines[0].match(/^\s*$/)) lines.shift();
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i];
lineNo.push(6 + i);
}
return {html: lines.join('\n'), hilite: lineNo.join(',') };
};
})();
+17 -8
View File
@@ -1,4 +1,7 @@
<h1><tt>{{name}}</tt></h1>
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
@@ -15,7 +18,7 @@ angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/param
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}{{#type}}({{type}}){{/type}}</tt>: {{description}}</li>
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
@@ -25,9 +28,15 @@ angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/param
<h3>CSS</h3>
{{{css}}}
<h2>Description</h2>
{{{description}}}
<WIKI:SOURCE style="display:block;">
{{{example}}}
</WIKI:SOURCE>
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+33
View File
@@ -0,0 +1,33 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
&lt;input type="text" ng:format="{{shortName}}"&gt;
</tt>
<h3>In JavaScript</h3>
<tt ng:non-bindable>
var userInputString = angular.formatter.{{shortName}}.format(modelValue);<br/>
var modelValue = angular.formatter.{{shortName}}.parse(userInputString);
</tt>
<h3>Returns</h3>
{{{returns}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+143 -19
View File
@@ -1,25 +1,149 @@
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
<html xmlns:ng="http://angularjs.org/" xmlns:doc="http://docs.angularjs.org/" ng:controller="DocsController">
<head>
<title>&lt;Angular/&gt; Docs</title>
<link rel="stylesheet" href="wiki_widgets.css" type="text/css" media="screen">
<link rel="stylesheet" href="doc_widgets.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css" media="screen" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="docs-data.js"></script>
<script type="text/javascript" src="../angular.min.js" ng:autobind></script>
<script type="text/javascript" src="http://angularjs.org/extensions/wiki_widgets.js"></script>
<link rel="stylesheet" href="http://angularjs.org/extensions/wiki_widgets.css" type="text/css" media="screen" />
<script type="text/javascript" src="doc_widgets.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script>
<script type="text/javascript" src="docs-data.js"></script>
<script type="text/javascript">
SyntaxHighlighter['defaults'].toolbar = false;
DocsController.$inject = ['$location', '$browser']
function DocsController($location, $browser) {
this.docs = NG_DOC;
window.$root = this.$root;
this.getUrl = function(page){
return '#' + encodeURIComponent(page.name);
};
this.getCurrentPartial = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialUrl = './' + $location.hashPath + '.html';
}
return this.partialUrl;
}
this.getTitle = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialTitle = $location.hashPath;
}
return this.partialTitle;
}
}
</script>
<style type="text/css" media="screen">
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
#sidebar {
width: 15em;
float: left;
}
#header {
background-color: #F2C200;
margin-bottom: 1em;
}
#header h1 {
font-weight: normal;
font-size: 30px;
line-height: 30px;
margin: 0;
padding: 10px 10px;
height: 30px;
}
#header .angular {
font-family: Courier New, monospace;
font-weight: bold;
}
#header h1 a {
color: black;
text-decoration: none;
}
#header h1 a:hover {
text-decoration: underline;
}
#section {
position: absolute;
z-index: -1;
width: 100%;
}
#section ng\:include {
margin: 0 1em 1em 15em;
display: block;
}
#section h1 {
font-family: monospace;
margin-top: 0;
}
#sidebar h2 {
font-size: 1.2em;
margin: 5px 5px 5px 0.8em;
}
#sidebar ul {
list-style-type: none;
/*TODO(esprehn): Can we just reset globally and not break examples?*/
margin: 0;
padding: 0;
}
#sidebar ul li {
margin: 0;
padding: 1px 1px 1px 1.5em;
}
.nav-section {
margin-left: 1em;
margin-top: 0.5em;
}
.section-title {
float: right;
}
</style>
<title>&lt;angular/&gt;: {{getTitle()}}</title>
</head>
<body ng:init="docs=$window.NG_DOC; $window.$root = $root">
<table>
<tr>
<td valign="top">
<div ng:repeat="(name, type) in docs.section">
<b>{{name}}</b>
<div ng:repeat="page in type">
<a href="#{{page.name}}"><tt>{{page.shortName}}</tt></a>
</div>
</div>
</td>
<td valign="top"><ng:include src="$location.hashPath + '.html' "></ng:include></td>
</tr>
</table>
<body>
<div id="header">
<h1>
<span class="section-title">{{getTitle()}}</span>
<a href="index.html"><span class="angular">&lt;angular/&gt;</span> Docs</a>
</h1>
</div>
<div id="sidebar" class="nav">
<div ng:repeat="(name, type) in docs.section" class="nav-section">
<h2>{{name}}</h2>
<ul>
<li ng:repeat="page in type.$orderBy('shortName')">
<a href="{{getUrl(page)}}" ng:click="">{{page.shortName}}</a>
</li>
</ul>
</div>
</div>
<div id="section">
<ng:include src="getCurrentPartial()"></ng:include>
</div>
</body>
</html>
</html>
+15 -1
View File
@@ -1 +1,15 @@
{{{description}}}
<h1>{{name}}</h1>
{{{description}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+83
View File
@@ -0,0 +1,83 @@
console.log(__dirname);
require.paths.push(__dirname + "/../");
require.paths.push(__dirname + "/../../");
var fs = require('fs');
var Script = process.binding('evals').Script;
var collect = load('docs/collect.js');
describe('collect', function(){
describe('markdown', function(){
it('should replace angular in markdown', function(){
expect(collect.markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
});
});
describe('TAG', function(){
var TAG = collect.TAG;
var doc;
beforeEach(function(){
doc = {};
});
describe('@param', function(){
it('should parse with no default', function(){
TAG.param(doc, 'param',
'{(number|string)} number Number \n to format.');
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'number',
'default' : undefined,
description : 'Number \n to format.' }]);
});
it('should parse with default', function(){
TAG.param(doc, 'param',
'{(number|string)=} [fractionSize=2] desc');
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
'default' : '2',
description : 'desc' }]);
});
});
describe('@describe', function(){
it('should support pre blocks', function(){
TAG.description(doc, 'description', '<pre class="brush: xml;" ng:non-bindable>abc</pre>');
expect(doc.description).toEqual('<pre class="brush: xml;" ng:non-bindable>abc</pre>');
});
describe('@example', function(){
it('should not remove {{}}', function(){
TAG.example(doc, 'example', 'text {{ abc }}');
expect(doc.example).toEqual('text {{ abc }}');
});
});
});
});
describe('trim', function(){
var trim = collect.trim;
it('should remove leading/trailing space', function(){
expect(trim(' \nabc\n ')).toEqual('abc');
});
it('should remove leading space on every line', function(){
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
});
});
});
function load(path){
var sandbox = {
require: require,
console: console,
__dirname: __dirname,
testmode: true
};
Script.runInNewContext(fs.readFileSync(path), sandbox, path);
return sandbox;
}
+21
View File
@@ -0,0 +1,21 @@
require.paths.push("./lib");
var jasmine = require('jasmine-1.0.1');
var sys = require('sys');
for(var key in jasmine) {
global[key] = jasmine[key];
}
var isVerbose = false;
var showColors = true;
process.argv.forEach(function(arg){
switch(arg) {
case '--color': showColors = true; break;
case '--noColor': showColors = false; break;
case '--verbose': isVerbose = true; break;
}
});
jasmine.executeSpecsInFolder(__dirname + '/spec', function(runner, log){
process.exit(runner.results().failedCount);
}, isVerbose, showColors);
+39
View File
@@ -0,0 +1,39 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
&lt;input type="text" ng:validate="{{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}]</i>{{/default}}{{/paramRest}}"/>
</tt>
<h3>In JavaScript</h3>
<tt ng:non-bindable>
angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {{name}}{{/default}}{{#default}}<i>[, {{name}}]</i>{{/default}}{{/paramRest}} );
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+39
View File
@@ -0,0 +1,39 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
<pre>
&lt;{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}&gt;{{#usageContent}}
{{usageContent}}
{{/usageContent}}&lt;/{{shortName}}&gt;
</pre>
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
-58
View File
@@ -1,58 +0,0 @@
WIKI\:SOURCE,
WIKI\:SOURCE>ul.tabs,
WIKI\:SOURCE>ul.tabs>li {
display: block;
margin: 0;
padding: 0;
}
WIKI\:SOURCE>ul.tabs {
margin: 0 !important;
padding: 0 !important;
}
WIKI\:SOURCE > ul.tabs > li.tab {
margin: 0 0 0 .8em !important;
display: inline-block;
border: 1px solid gray;
padding: .1em .8em .4em .8em !important;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
border-bottom: none;
cursor: pointer;
background-color: lightgray;
margin-bottom: -2px;
position: relative;
z-index: 0;
}
WIKI\:SOURCE > ul.tabs > li.tab.selected {
z-index: 2;
background-color: white;
font-weight: bold;
border-width: 2px;
}
WIKI\:SOURCE > ul.tabs > li.pane {
border: 2px solid gray;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
background-color: white;
padding: 5px !important;
}
WIKI\:SOURCE > ul.tabs > li.pane {
display:none;
}
WIKI\:SOURCE > ul.tabs > li.pane.selected {
position: relative;
z-index: 1;
display:block;
}
WIKI\:SOURCE > ul.tabs > li.pane.source {
font-size: .8em !important;
}
//////////////////
-51
View File
@@ -1,51 +0,0 @@
(function(){
var HTML_TEMPLATE =
'<!DOCTYPE HTML>\n' +
'<html xmlns:ng="http://angularjs.org">\n' +
' <head>\n' +
' <script type="text/javascript"\n' +
' src="http://angularjs.org/ng/js/angular-debug.js" ng:autobind></script>\n' +
' </head>\n' +
' <body>\n' +
'_HTML_SOURCE_\n' +
' </body>\n' +
'</html>';
angular.widget('WIKI:SOURCE', function(element){
this.descend(true);
var html = element.text();
element.show();
var tabs = angular.element(
'<ul class="tabs">' +
'<li class="tab selected" to="angular">&lt;angular/&gt;</li>' +
'<li class="tab" to="plain">plain</li>' +
'<li class="tab" to="source">source</li>' +
'<li class="pane selected angular">' + html + '</li>' +
'<li class="pane plain" ng:non-bindable>' + html + '</li>' +
'<li class="pane source" ng:non-bindable><pre class="brush: js; html-script: true"></pre></li>' +
'</ul>');
var pre = tabs.
find('>li.source>pre').
text(HTML_TEMPLATE.replace('_HTML_SOURCE_', html));
var color = element.attr('color') || 'white';
element.html('');
element.append(tabs);
element.find('>ul.tabs>li.pane').css('background-color', color);
var script = (html.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
try {
eval(script);
} catch (e) {
alert(e);
}
return function(element){
element.find('>ul.tabs>li.tab').click(function(){
if ($(this).is(".selected")) return;
element.
find('>ul.tabs>li.selected').
add(this).
add(element.find('>ul>li.pane.' + angular.element(this).attr('to'))).
toggleClass('selected');
});
};
});
})();
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
/usr/local/bin/node docs/collect.js
node docs/specs.js --noColor && node docs/collect.js
+180
View File
@@ -0,0 +1,180 @@
var fs = require('fs');
var sys = require('sys');
var path = require('path');
var filename = __dirname + '/jasmine.js';
global.window = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
};
var src = fs.readFileSync(filename);
var jasmine = process.compile(src + '\njasmine;', filename);
delete global.window;
function noop(){}
jasmine.executeSpecsInFolder = function(folder, done, isVerbose, showColors, matcher){
var log = [];
var columnCounter = 0;
var start = 0;
var elapsed = 0;
var verbose = isVerbose || false;
var fileMatcher = new RegExp(matcher || "\.js$");
var colors = showColors || false;
var specs = jasmine.getAllSpecFiles(folder, fileMatcher);
var ansi = {
green: '\033[32m',
red: '\033[31m',
yellow: '\033[33m',
none: '\033[0m'
};
for (var i = 0, len = specs.length; i < len; ++i){
var filename = specs[i];
require(filename.replace(/\.*$/, ""));
}
var jasmineEnv = jasmine.getEnv();
jasmineEnv.reporter = {
log: function(str){
},
reportSpecStarting: function(runner) {
},
reportRunnerStarting: function(runner) {
sys.puts('Started');
start = Number(new Date);
},
reportSuiteResults: function(suite) {
var specResults = suite.results();
var path = [];
while(suite) {
path.unshift(suite.description);
suite = suite.parentSuite;
}
var description = path.join(' ');
if (verbose)
log.push('Spec ' + description);
specResults.items_.forEach(function(spec){
if (spec.failedCount > 0 && spec.description) {
if (!verbose)
log.push(description);
log.push(' it ' + spec.description);
spec.items_.forEach(function(result){
log.push(' ' + result.trace.stack + '\n');
});
}
});
},
reportSpecResults: function(spec) {
var result = spec.results();
var msg = '';
if (result.passed())
{
msg = (colors) ? (ansi.green + '.' + ansi.none) : '.';
// } else if (result.skipped) { TODO: Research why "result.skipped" returns false when "xit" is called on a spec?
// msg = (colors) ? (ansi.yellow + '*' + ansi.none) : '*';
} else {
msg = (colors) ? (ansi.red + 'F' + ansi.none) : 'F';
}
sys.print(msg);
if (columnCounter++ < 50) return;
columnCounter = 0;
sys.print('\n');
},
reportRunnerResults: function(runner) {
elapsed = (Number(new Date) - start) / 1000;
sys.puts('\n');
log.forEach(function(log){
sys.puts(log);
});
sys.puts('Finished in ' + elapsed + ' seconds');
var summary = jasmine.printRunnerResults(runner);
if(colors)
{
if(runner.results().failedCount === 0 )
sys.puts(ansi.green + summary + ansi.none);
else
sys.puts(ansi.red + summary + ansi.none);
} else {
sys.puts(summary);
}
(done||noop)(runner, log);
}
};
jasmineEnv.execute();
};
jasmine.getAllSpecFiles = function(dir, matcher){
var specs = [];
if (fs.statSync(dir).isFile() && dir.match(matcher)) {
specs.push(dir);
} else {
var files = fs.readdirSync(dir);
for (var i = 0, len = files.length; i < len; ++i){
var filename = dir + '/' + files[i];
if (fs.statSync(filename).isFile() && filename.match(matcher)){
specs.push(filename);
}else if (fs.statSync(filename).isDirectory()){
var subfiles = this.getAllSpecFiles(filename, matcher);
subfiles.forEach(function(result){
specs.push(result);
});
}
}
}
return specs;
};
jasmine.printRunnerResults = function(runner){
var results = runner.results();
var suites = runner.suites();
var msg = '';
msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', ';
msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', ';
msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n';
return msg;
};
function now(){
return new Date().getTime();
}
jasmine.asyncSpecWait = function(){
var wait = jasmine.asyncSpecWait;
wait.start = now();
wait.done = false;
(function innerWait(){
waits(10);
runs(function() {
if (wait.start + wait.timeout < now()) {
expect('timeout waiting for spec').toBeNull();
} else if (wait.done) {
wait.done = false;
} else {
innerWait();
}
});
})();
};
jasmine.asyncSpecWait.timeout = 4 * 1000;
jasmine.asyncSpecDone = function(){
jasmine.asyncSpecWait.done = true;
};
for ( var key in jasmine) {
exports[key] = jasmine[key];
}
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
var fs = require('fs');
var filename = __dirname + '/showdown-0.9.js';
var src = fs.readFileSync(filename);
var Showdown = process.compile(src + '\nShowdown;', filename);
exports.Showdown = Showdown;
File diff suppressed because it is too large Load Diff
+254 -20
View File
@@ -17,7 +17,7 @@ var lowercase = function (value){ return isString(value) ? value.toLowerCase() :
/**
* @ngdoc
* @name angular#uppercase
* @name angular.uppercase
* @function
*
* @description Converts string to uppercase.
@@ -92,7 +92,157 @@ var _undefined = undefined,
angularTextMarkup = extensionMap(angular, 'markup'),
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
angularDirective = extensionMap(angular, 'directive'),
/**
* @ngdoc overview
* @name angular.widget
* @namespace Namespace for all widgets.
* @description
* # Overview
* Widgets allow you to create DOM elements that the browser doesn't
* already understand. You create the widget in your namespace and
* assign it behavior. You can only bind one widget per DOM element
* (unlike directives, in which you can use any number per DOM
* element). Widgets are expected to manipulate the DOM tree by
* adding new elements whereas directives are expected to only modify
* element properties.
*
* Widgets come in two flavors: element and attribute.
*
* # Element Widget
* Let's say we would like to create a new element type in the
* namespace `my` that can watch an expression and alert() the user
* with each new value.
*
* <pre>
* &lt;my:watch exp="name"/&gt;
* </pre>
*
* You can implement `my:watch` like this:
* <pre>
* angular.widget('my:watch', function(compileElement) {
* var compiler = this;
* var exp = compileElement.attr('exp');
* return function(linkElement) {
* var currentScope = this;
* currentScope.$watch(exp, function(value){
* alert(value);
* }};
* };
* });
* </pre>
*
* # Attribute Widget
* Let's implement the same widget, but this time as an attribute
* that can be added to any existing DOM element.
* <pre>
* &lt;div my-watch="name"&gt;text&lt;/div&gt;
* </pre>
* You can implement `my:watch` attribute like this:
* <pre>
* angular.widget('@my:watch', function(expression, compileElement) {
* var compiler = this;
* return function(linkElement) {
* var currentScope = this;
* currentScope.$watch(expression, function(value){
* alert(value);
* });
* };
* });
* </pre>
*
* @example
* <script>
* angular.widget('my:time', function(compileElement){
* compileElement.css('display', 'block');
* return function(linkElement){
* function update(){
* linkElement.text('Current time is: ' + new Date());
* setTimeout(update, 1000);
* }
* update();
* };
* });
* </script>
* <my:time></my:time>
*/
angularWidget = extensionMap(angular, 'widget', lowercase),
/**
* @ngdoc overview
* @name angular.validator
* @namespace Namespace for all filters.
* @description
* # Overview
* Validators are a standard way to check the user input against a specific criteria. For
* example, you might need to check that an input field contains a well-formed phone number.
*
* # Syntax
* Attach a validator on user input widgets using the `ng:validate` attribute.
*
* <doc:example>
* <doc:source>
* Change me: &lt;input type="text" name="number" ng:validate="integer" value="123"&gt;
* </doc:source>
* <doc:scenario>
* it('should validate the default number string', function() {
* expect(element('input[name=number]').attr('class')).
* not().toMatch(/ng-validation-error/);
* });
* it('should not validate "foo"', function() {
* input('number').enter('foo');
* expect(element('input[name=number]').attr('class')).
* toMatch(/ng-validation-error/);
* });
* </doc:scenario>
* </doc:example>
*
*
* # Writing your own Validators
* Writing your own validator is easy. To make a function available as a
* validator, just define the JavaScript function on the `angular.validator`
* object. <angular/> passes in the input to validate as the first argument
* to your function. Any additional validator arguments are passed in as
* additional arguments to your function.
*
* You can use these variables in the function:
*
* * `this` — The current scope.
* * `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
* the DOM in addition to transforming the input.
*
* In this example we have written a upsTrackingNo validator.
* It marks the input text "valid" only when the user enters a well-formed
* UPS tracking number.
*
* @css ng-validation-error
* When validation fails, this css class is applied to the binding, making its borders red by
* default.
*
* @example
* <script>
* angular.validator('upsTrackingNo', function(input, format) {
* var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
* return input.match(regexp)?"":"The format must match " + format;
* });
* </script>
* <input type="text" name="trackNo" size="40"
* ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
* value="1Z 123 456 78 9012 345 6"/>
*
* @scenario
* it('should validate correct UPS tracking number', function() {
* expect(element('input[name=trackNo]').attr('class')).
* not().toMatch(/ng-validation-error/);
* });
*
* it('should not validate in correct UPS tracking number', function() {
* input('trackNo').enter('foo');
* expect(element('input[name=trackNo]').attr('class')).
* toMatch(/ng-validation-error/);
* });
*
*/
angularValidator = extensionMap(angular, 'validator'),
@@ -139,20 +289,18 @@ var _undefined = undefined,
* You can use these variables in the function:
*
* * `this` — The current scope.
* * `$element` — The DOM element containing the binding. This allows the filter to manipulate
* * `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
* the DOM in addition to transforming the input.
*
*
* @example
* //TODO this example current doesn't show up anywhere because the overview template doesn't
* // render it.
* @exampleDescription
* The following example filter reverses a text string. In addition, it conditionally makes the
* text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
* modification).
*
* The following example filter reverses a text string. In addition, it conditionally makes the
* text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
* modification).
* @example
<script type="text/javascript">
angular.filter.reverse = function(input, uppercase, color) {
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
@@ -164,19 +312,96 @@ var _undefined = undefined,
this.$element.css('color', color);
}
return out;
};
});
</script>
<span ng:non-bindable="true">{{"hello"|reverse}}</span>: {{"hello"|reverse}}<br>
<span ng:non-bindable="true">{{"hello"|reverse:true}}</span>: {{"hello"|reverse:true}}<br>
<span ng:non-bindable="true">{{"hello"|reverse:true:"blue"}}</span>:
{{"hello"|reverse:true:"blue"}}
* //TODO: I completely dropped a mention of using the other option (setter method), it's
* confusing to have two ways to do the same thing. I just wonder if we should prefer using the
* setter way over direct assignment because in the future we might want to be able to intercept
* filter registrations for some reason.
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
*/
angularFilter = extensionMap(angular, 'filter'),
/**
* @ngdoc overview
* @name angular.formatter
* @namespace Namespace for all formats.
* @description
* # Overview
* The formatters are responsible for translating user readable text in an input widget to a
* data model stored in an application.
*
* # Writting your own Fromatter
* Writing your own formatter is easy. Just register a pair of JavaScript functions with
* `angular.formatter`. One function for parsing user input text to the stored form,
* and one for formatting the stored data to user-visible text.
*
* Here is an example of a "reverse" formatter: The data is stored in uppercase and in
* reverse, while it is displayed in lower case and non-reversed. User edits are
* automatically parsed into the internal form and data changes are automatically
* formatted to the viewed form.
*
* <pre>
* function reverse(text) {
* var reversed = [];
* for (var i = 0; i < text.length; i++) {
* reversed.unshift(text.charAt(i));
* }
* return reversed.join('');
* }
*
* angular.formatter('reverse', {
* parse: function(value){
* return reverse(value||'').toUpperCase();
* },
* format: function(value){
* return reverse(value||'').toLowerCase();
* }
* });
* </pre>
*
* @example
* <script type="text/javascript">
* function reverse(text) {
* var reversed = [];
* for (var i = 0; i < text.length; i++) {
* reversed.unshift(text.charAt(i));
* }
* return reversed.join('');
* }
*
* angular.formatter('reverse', {
* parse: function(value){
* return reverse(value||'').toUpperCase();
* },
* format: function(value){
* return reverse(value||'').toLowerCase();
* }
* });
* </script>
*
* Formatted:
* <input type="text" name="data" value="angular" ng:format="reverse"/>
* <br/>
*
* Stored:
* <input type="text" name="data"/><br/>
* <pre>{{data}}</pre>
*
*
* @scenario
* it('should store reverse', function(){
* expect(element('.doc-example input:first').val()).toEqual('angular');
* expect(element('.doc-example input:last').val()).toEqual('RALUGNA');
*
* this.addFutureAction('change to XYZ', function($window, $document, done){
* $document.elements('.doc-example input:last').val('XYZ').trigger('change');
* done();
* });
* expect(element('input:first').val()).toEqual('zyx');
* });
*/
angularFormatter = extensionMap(angular, 'formatter'),
angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'),
@@ -234,11 +459,18 @@ function inherit(parent, extra) {
function noop() {}
function identity($) {return $;}
function valueFn(value) {return function(){ return value; };}
function extensionMap(angular, name, transform) {
var extPoint;
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
name = (transform || identity)(name);
if (isDefined(fn)) {
if (isDefined(extPoint[name])) {
foreach(extPoint[name], function(property, key) {
if (key.charAt(0) == '$' && isUndefined(fn[key]))
fn[key] = property;
});
}
extPoint[name] = extend(fn, prop || {});
}
return extPoint[name];
@@ -263,8 +495,10 @@ function isDefined(value){ return typeof value != $undefined; }
function isObject(value){ return value!=_null && typeof value == $object;}
function isString(value){ return typeof value == $string;}
function isNumber(value){ return typeof value == $number;}
function isDate(value){ return value instanceof Date; }
function isArray(value) { return value instanceof Array; }
function isFunction(value){ return typeof value == $function;}
function isBoolean(value) { return typeof value == $boolean;}
function isTextNode(node) { return nodeName(node) == '#text'; }
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
function isElement(node) {
@@ -376,7 +610,7 @@ function copy(source, destination){
if (source) {
if (isArray(source)) {
destination = copy(source, []);
} else if (source instanceof Date) {
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isObject(source)) {
destination = copy(source, {});
+24 -15
View File
@@ -1,6 +1,6 @@
var array = [].constructor;
function toJson(obj, pretty){
function toJson(obj, pretty) {
var buf = [];
toJsonArray(buf, obj, pretty ? "\n " : _null, []);
return buf.join('');
@@ -22,40 +22,49 @@ function fromJson(json) {
angular['toJson'] = toJson;
angular['fromJson'] = fromJson;
function toJsonArray(buf, obj, pretty, stack){
if (typeof obj == "object") {
function toJsonArray(buf, obj, pretty, stack) {
if (isObject(obj)) {
if (obj === window) {
buf.push('WINDOW');
return;
}
if (obj === document) {
buf.push('DOCUMENT');
return;
}
if (includes(stack, obj)) {
buf.push("RECURSION");
buf.push('RECURSION');
return;
}
stack.push(obj);
}
var type = typeof obj;
if (obj === _null) {
buf.push($null);
} else if (obj instanceof RegExp) {
buf.push(angular['String']['quoteUnicode'](obj.toString()));
} else if (type === $function) {
} else if (isFunction(obj)) {
return;
} else if (type === $boolean) {
} else if (isBoolean(obj)) {
buf.push('' + obj);
} else if (type === $number) {
} else if (isNumber(obj)) {
if (isNaN(obj)) {
buf.push($null);
} else {
buf.push('' + obj);
}
} else if (type === $string) {
} else if (isString(obj)) {
return buf.push(angular['String']['quoteUnicode'](obj));
} else if (type === $object) {
if (obj instanceof Array) {
} else if (isObject(obj)) {
if (isArray(obj)) {
buf.push("[");
var len = obj.length;
var sep = false;
for(var i=0; i<len; i++) {
var item = obj[i];
if (sep) buf.push(",");
if (!(item instanceof RegExp) && (typeof item == $function || typeof item == $undefined)) {
if (!(item instanceof RegExp) && (isFunction(item) || isUndefined(item))) {
buf.push($null);
} else {
toJsonArray(buf, item, pretty, stack);
@@ -63,7 +72,7 @@ function toJsonArray(buf, obj, pretty, stack){
sep = true;
}
buf.push("]");
} else if (obj instanceof Date) {
} else if (isDate(obj)) {
buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj)));
} else {
buf.push("{");
@@ -72,7 +81,7 @@ function toJsonArray(buf, obj, pretty, stack){
var childPretty = pretty ? pretty + " " : false;
var keys = [];
for(var k in obj) {
if (k.indexOf('$') === 0 || obj[k] === _undefined)
if (obj[k] === _undefined)
continue;
keys.push(k);
}
@@ -94,7 +103,7 @@ function toJsonArray(buf, obj, pretty, stack){
buf.push("}");
}
}
if (typeof obj == $object) {
if (isObject(obj)) {
stack.pop();
}
}
+1 -1
View File
@@ -167,7 +167,7 @@ function createScope(parent, providers, instanceCache) {
$watch: function(watchExp, listener, exceptionHandler) {
var watch = expressionCompile(watchExp),
last;
last = {};
listener = expressionCompile(listener);
function watcher(){
var value = watch.call(instance),
+16 -12
View File
@@ -4,7 +4,7 @@ var angularGlobal = {
var type = typeof obj;
if (type == $object) {
if (obj instanceof Array) return $array;
if (obj instanceof Date) return $date;
if (isDate(obj)) return $date;
if (obj.nodeType == 1) return $element;
}
return type;
@@ -177,6 +177,8 @@ var angularArray = {
}
};
var R_ISO8061_STR = /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3}))?)?)?Z)?$/
var angularString = {
'quote':function(string) {
return '"' + string.replace(/\\/g, '\\\\').
@@ -210,11 +212,10 @@ var angularString = {
*/
'toDate':function(string){
var match;
if (typeof string == 'string' &&
(match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){
if (isString(string) && (match = string.match(R_ISO8061_STR))){
var date = new Date(0);
date.setUTCFullYear(match[1], match[2] - 1, match[3]);
date.setUTCHours(match[4], match[5], match[6], 0);
date.setUTCHours(match[4]||0, match[5]||0, match[6]||0, match[7]||0);
return date;
}
return string;
@@ -223,14 +224,17 @@ var angularString = {
var angularDate = {
'toString':function(date){
function pad(n) { return n < 10 ? "0" + n : n; }
return !date ? date :
date.getUTCFullYear() + '-' +
pad(date.getUTCMonth() + 1) + '-' +
pad(date.getUTCDate()) + 'T' +
pad(date.getUTCHours()) + ':' +
pad(date.getUTCMinutes()) + ':' +
pad(date.getUTCSeconds()) + 'Z' ;
return !date ?
date :
date.toISOString ?
date.toISOString() :
padNumber(date.getUTCFullYear(), 4) + '-' +
padNumber(date.getUTCMonth() + 1, 2) + '-' +
padNumber(date.getUTCDate(), 2) + 'T' +
padNumber(date.getUTCHours(), 2) + ':' +
padNumber(date.getUTCMinutes(), 2) + ':' +
padNumber(date.getUTCSeconds(), 2) + '.' +
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
}
};
+585 -3
View File
@@ -1,9 +1,97 @@
/**
* @ngdoc directive
* @name angular.directive.ng:init
*
* @description
* `ng:init` attribute allows the for initialization tasks to be executed
* before the template enters execution mode during bootstrap.
*
* @element ANY
* @param {expression} expression to eval.
*
* @example
<div ng:init="greeting='Hello'; person='World'">
{{greeting}} {{person}}!
</div>
*
* @scenario
it('should check greeting', function(){
expect(binding('greeting')).toBe('Hello');
expect(binding('person')).toBe('World');
});
*/
angularDirective("ng:init", function(expression){
return function(element){
this.$tryEval(expression, element);
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:controller
*
* @description
* To support the Model-View-Controller design pattern, it is possible
* to assign behavior to a scope through `ng:controller`. The scope is
* the MVC model. The HTML (with data bindings) is the MVC view.
* The `ng:controller` directive specifies the MVC controller class
*
* @element ANY
* @param {expression} expression to eval.
*
* @example
<script type="text/javascript">
function SettingsController() {
this.name = "John Smith";
this.contacts = [
{type:'phone', value:'408 555 1212'},
{type:'email', value:'john.smith@example.org'} ];
}
SettingsController.prototype = {
greet: function(){
alert(this.name);
},
addContact: function(){
this.contacts.push({type:'email', value:'yourname@example.org'});
},
removeContact: function(contactToRemove) {
angular.Array.remove(this.contacts, contactToRemove);
},
clearContact: function(contact) {
contact.type = 'phone';
contact.value = '';
}
};
</script>
<div ng:controller="SettingsController">
Name: <input type="text" name="name"/>
[ <a href="" ng:click="greet()">greet</a> ]<br/>
Contact:
<ul>
<li ng:repeat="contact in contacts">
<select name="contact.type">
<option>phone</option>
<option>email</option>
</select>
<input type="text" name="contact.value"/>
[ <a href="" ng:click="clearContact(contact)">clear</a>
| <a href="" ng:click="removeContact(contact)">X</a> ]
</li>
<li>[ <a href="" ng:click="addContact()">add</a> ]</li>
</ul>
</div>
*
* @scenario
it('should check controller', function(){
expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212');
expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org');
element('.doc-example-live li:first a:contains("clear")').click();
expect(element('.doc-example-live li:first input').val()).toBe('');
element('.doc-example-live li:last a:contains("add")').click();
expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org');
});
*/
angularDirective("ng:controller", function(expression){
this.scope(true);
return function(element){
@@ -16,12 +104,75 @@ angularDirective("ng:controller", function(expression){
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:eval
*
* @description
* The `ng:eval` allows you to execute a binding which has side effects
* without displaying the result to the user.
*
* @element ANY
* @param {expression} expression to eval.
*
* @exampleDescription
* Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning
* a value to `obj.multiplied` and displaying the result to the user. Sometimes,
* however, it is desirable to execute a side effect without showing the value to
* the user. In such a case `ng:eval` allows you to execute code without updating
* the display.
*
* @example
* <input name="obj.a" value="6" >
* * <input name="obj.b" value="2">
* = {{obj.multiplied = obj.a * obj.b}} <br>
* <span ng:eval="obj.divide = obj.a / obj.b"></span>
* <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span>
* <tt>obj.divide = {{obj.divide}}</tt><br/>
* <tt>obj.updateCount = {{obj.updateCount}}</tt>
*
* @scenario
it('should check eval', function(){
expect(binding('obj.divide')).toBe('3');
expect(binding('obj.updateCount')).toBe('2');
input('obj.a').enter('12');
expect(binding('obj.divide')).toBe('6');
expect(binding('obj.updateCount')).toBe('3');
});
*/
angularDirective("ng:eval", function(expression){
return function(element){
this.$onEval(expression, element);
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:bind
*
* @description
* The `ng:bind` attribute asks <angular/> to replace the text content of this
* HTML element with the value of the given expression and kept it up to
* date when the expression's value changes. Usually you just write
* {{expression}} and let <angular/> compile it into
* <span ng:bind="expression"></span> at bootstrap time.
*
* @element ANY
* @param {expression} expression to eval.
*
* @exampleDescription
* Try it here: enter text in text box and watch the greeting change.
* @example
* Enter name: <input type="text" name="name" value="Whirled">. <br>
* Hello <span ng:bind="name" />!
*
* @scenario
it('should check ng:bind', function(){
expect(using('.doc-example-live').binding('name')).toBe('Whirled');
using('.doc-example-live').input('name').enter('world');
expect(using('.doc-example-live').binding('name')).toBe('world');
});
*/
angularDirective("ng:bind", function(expression, element){
element.addClass('ng-binding');
return function(element) {
@@ -98,6 +249,38 @@ function compileBindTemplate(template){
return fn;
}
/**
* @ngdoc directive
* @name angular.directive.ng:bind-template
*
* @description
* The `ng:bind-template` attribute specifies that the element
* text should be replaced with the template in ng:bind-template.
* Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}`
* expressions. (This is required since some HTML elements
* can not have SPAN elements such as TITLE, or OPTION to name a few.
*
* @element ANY
* @param {string} template of form
* <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
*
* @exampleDescription
* Try it here: enter text in text box and watch the greeting change.
* @example
Salutation: <input type="text" name="salutation" value="Hello"><br/>
Name: <input type="text" name="name" value="World"><br/>
<pre ng:bind-template="{{salutation}} {{name}}!"></pre>
*
* @scenario
it('should check ng:bind', function(){
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
toBe('Hello World!');
using('.doc-example-live').input('salutation').enter('Greetings');
using('.doc-example-live').input('name').enter('user');
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
toBe('Greetings user!');
});
*/
angularDirective("ng:bind-template", function(expression, element){
element.addClass('ng-binding');
var templateFn = compileBindTemplate(expression);
@@ -118,6 +301,55 @@ var REMOVE_ATTRIBUTES = {
'readonly':'readOnly',
'checked':'checked'
};
/**
* @ngdoc directive
* @name angular.directive.ng:bind-attr
*
* @description
* The `ng:bind-attr` attribute specifies that the element attributes
* which should be replaced by the expression in it. Unlike `ng:bind`
* the `ng:bind-attr` contains a JSON key value pairs representing
* which attributes need to be changed. You dont usually write the
* `ng:bind-attr` in the HTML since embedding
* <tt ng:non-bindable>{{expression}}</tt> into the
* attribute directly is the preferred way. The attributes get
* translated into <span ng:bind-attr="{attr:expression}"/> at
* bootstrap time.
*
* This HTML snippet is preferred way of working with `ng:bind-attr`
* <pre>
* <a href="http://www.google.com/search?q={{query}}">Google</a>
* </pre>
*
* The above gets translated to bellow during bootstrap time.
* <pre>
* <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a>
* </pre>
*
* @element ANY
* @param {string} attribute_json a JSON key-value pairs representing
* the attributes to replace. Each key matches the attribute
* which needs to be replaced. Each value is a text template of
* the attribute with embedded
* <tt ng:non-bindable>{{expression}}</tt>s. Any number of
* key-value pairs can be specified.
*
* @exampleDescription
* Try it here: enter text in text box and click Google.
* @example
Google for:
<input type="text" name="query" value="AngularJS"/>
<a href="http://www.google.com/search?q={{query}}">Google</a>
*
* @scenario
it('should check ng:bind-attr', function(){
expect(using('.doc-example-live').element('a').attr('href')).
toBe('http://www.google.com/search?q=AngularJS');
using('.doc-example-live').input('query').enter('google');
expect(using('.doc-example-live').element('a').attr('href')).
toBe('http://www.google.com/search?q=google');
});
*/
angularDirective("ng:bind-attr", function(expression){
return function(element){
var lastValue = {};
@@ -146,8 +378,86 @@ angularDirective("ng:bind-attr", function(expression){
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:non-bindable
*
* @description
* Sometimes it is necessary to write code which looks like
* bindings but which should be left alone by <angular/>.
* Use `ng:non-bindable` to ignore a chunk of HTML.
*
* @element ANY
* @param {string} ignore
*
* @exampleDescription
* In this example there are two location where
* <tt ng:non-bindable>{{1 + 2}}</tt> is present, but the one
* wrapped in `ng:non-bindable` is left alone
* @example
<div>Normal: {{1 + 2}}</div>
<div ng:non-bindable>Ignored: {{1 + 2}}</div>
*
* @scenario
it('should check ng:non-bindable', function(){
expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
expect(using('.doc-example-live').element('div:last').text()).
toMatch(/1 \+ 2/);
});
*/
angularWidget("@ng:non-bindable", noop);
/**
* @ngdoc directive
* @name angular.directive.ng:repeat
*
* @description
* `ng:repeat` instantiates a template once per item from a
* collection. The collection is enumerated with
* `ng:repeat-index` attribute starting from 0. Each template
* instance gets its own scope where the given loop variable
* is set to the current collection item and `$index` is set
* to the item index or key.
*
* NOTE: `ng:repeat` looks like a directive, but is actually a
* attribute widget.
*
* @element ANY
* @param {repeat} repeat_expression to itterate over.
*
* * `variable in expression`, where variable is the user
* defined loop variable and expression is a scope expression
* giving the collection to enumerate. For example:
* `track in cd.tracks`.
* * `(key, value) in expression`, where key and value can
* be any user defined identifiers, and expression is the
* scope expression giving the collection to enumerate.
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
* Special properties set on the local scope:
* * {number} $index - iterator offset of the repeated element (0..length-1)
* * {string} $position - position of the repeated element in the iterator ('first', 'middle', 'last')
*
* @exampleDescription
* This example initializes the scope to a list of names and
* than uses `ng:repeat` to display every person.
* @example
<div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
I have {{friends.length}} friends. They are:
<ul>
<li ng:repeat="friend in friends">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
* @scenario
it('should check ng:repeat', function(){
var r = using('.doc-example-live').repeater('ul li');
expect(r.count()).toBe(2);
expect(r.row(0)).toEqual(["1","John","25"]);
expect(r.row(1)).toEqual(["2","Mary","28"]);
});
*/
angularWidget("@ng:repeat", function(expression, element){
element.removeAttr('ng:repeat');
element.replaceWith(this.comment("ng:repeat: " + expression));
@@ -171,9 +481,24 @@ angularWidget("@ng:repeat", function(expression, element){
var children = [], currentScope = this;
this.$onEval(function(){
var index = 0, childCount = children.length, childScope, lastElement = reference,
collection = this.$tryEval(rhs, reference), is_array = isArray(collection);
for ( var key in collection) {
var index = 0,
childCount = children.length,
lastElement = reference,
collection = this.$tryEval(rhs, reference),
is_array = isArray(collection),
collectionLength = 0,
childScope,
key;
if (is_array) {
collectionLength = collection.length;
} else {
for (key in collection)
if (collection.hasOwnProperty(key))
collectionLength++;
}
for (key in collection) {
if (!is_array || collection.hasOwnProperty(key)) {
if (index < childCount) {
// reuse existing child
@@ -187,6 +512,9 @@ angularWidget("@ng:repeat", function(expression, element){
if (keyIdent) childScope[keyIdent] = key;
lastElement.after(childScope.$element);
childScope.$index = index;
childScope.$position = index == 0 ?
'first' :
(index == collectionLength - 1 ? 'last' : 'middle');
childScope.$element.attr('ng:repeat-index', index);
childScope.$init();
children.push(childScope);
@@ -205,6 +533,29 @@ angularWidget("@ng:repeat", function(expression, element){
});
/**
* @ngdoc directive
* @name angular.directive.ng:click
*
* @description
* The ng:click allows you to specify custom behavior when
* element is clicked.
*
* @element ANY
* @param {expression} expression to eval upon click.
*
* @example
<button ng:click="count = count + 1" ng:init="count=0">
Increment
</button>
count: {{count}}
* @scenario
it('should check ng:click', function(){
expect(binding('count')).toBe('0');
element('.doc-example-live :button').click();
expect(binding('count')).toBe('1');
});
*/
/*
* A directive that allows creation of custom onclick handlers that are defined as angular
* expressions and are compiled and executed within the current scope.
@@ -225,6 +576,35 @@ angularDirective("ng:click", function(expression, element){
});
/**
* @ngdoc directive
* @name angular.directive.ng:submit
*
* @description
*
* @element form
* @param {expression} expression to eval.
*
* @exampleDescription
* @example
* <form ng:submit="list.push(text);text='';" ng:init="list=[]">
* Enter text and hit enter:
* <input type="text" name="text" value="hello"/>
* </form>
* <pre>list={{list}}</pre>
* @scenario
it('should check ng:submit', function(){
expect(binding('list')).toBe('list=[]');
element('.doc-example-live form input').click();
this.addFutureAction('submit from', function($window, $document, done) {
$window.angular.element(
$document.elements('.doc-example-live form')).
trigger('submit');
done();
});
expect(binding('list')).toBe('list=["hello"]');
});
*/
/**
* Enables binding angular expressions to onsubmit events.
*
@@ -243,6 +623,32 @@ angularDirective("ng:submit", function(expression, element) {
});
/**
* @ngdoc directive
* @name angular.directive.ng:watch
*
* @description
* The `ng:watch` allows you watch a variable and then execute
* an evaluation on variable change.
*
* @element ANY
* @param {expression} expression to eval.
*
* @exampleDescription
* Notice that the counter is incremented
* every time you change the text.
* @example
<div ng:init="counter=0" ng:watch="name: counter = counter+1">
<input type="text" name="name" value="hello"><br/>
Change counter: {{counter}} Name: {{name}}
</div>
* @scenario
it('should check ng:watch', function(){
expect(using('.doc-example-live').binding('counter')).toBe('2');
using('.doc-example-live').input('name').enter('abc');
expect(using('.doc-example-live').binding('counter')).toBe('3');
});
*/
angularDirective("ng:watch", function(expression, element){
return function(element){
var self = this;
@@ -271,10 +677,141 @@ function ngClass(selector) {
};
}
/**
* @ngdoc directive
* @name angular.directive.ng:class
*
* @description
* The `ng:class` allows you to set CSS class on HTML element
* conditionally.
*
* @element ANY
* @param {expression} expression to eval.
*
* @exampleDescription
* @example
<input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'">
<input type="button" value="clear" ng:click="myVar=''">
<br>
<span ng:class="myVar">Sample Text &nbsp;&nbsp;&nbsp;&nbsp;</span>
*
* @scenario
it('should check ng:class', function(){
expect(element('.doc-example-live span').attr('className')).not().
toMatch(/ng-input-indicator-wait/);
using('.doc-example-live').element(':button:first').click();
expect(element('.doc-example-live span').attr('className')).
toMatch(/ng-input-indicator-wait/);
using('.doc-example-live').element(':button:last').click();
expect(element('.doc-example-live span').attr('className')).not().
toMatch(/ng-input-indicator-wait/);
});
*/
angularDirective("ng:class", ngClass(function(){return true;}));
/**
* @ngdoc directive
* @name angular.directive.ng:class-odd
*
* @description
* The `ng:class-odd` and `ng:class-even` works exactly as
* `ng:class`, except it works in conjunction with `ng:repeat`
* and takes affect only on odd (even) rows.
*
* @element ANY
* @param {expression} expression to eval. Must be inside
* `ng:repeat`.
*
* @exampleDescription
* @example
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
<li ng:repeat="name in names">
<span ng:class-odd="'ng-format-negative'"
ng:class-even="'ng-input-indicator-wait'">
{{name}} &nbsp; &nbsp; &nbsp;
</span>
</li>
</ol>
*
* @scenario
it('should check ng:class-odd and ng:class-even', function(){
expect(element('.doc-example-live li:first span').attr('className')).
toMatch(/ng-format-negative/);
expect(element('.doc-example-live li:last span').attr('className')).
toMatch(/ng-input-indicator-wait/);
});
*/
angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
/**
* @ngdoc directive
* @name angular.directive.ng:class-even
*
* @description
* The `ng:class-odd` and `ng:class-even` works exactly as
* `ng:class`, except it works in conjunction with `ng:repeat`
* and takes affect only on odd (even) rows.
*
* @element ANY
* @param {expression} expression to eval. Must be inside
* `ng:repeat`.
*
* @exampleDescription
* @example
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
<li ng:repeat="name in names">
<span ng:class-odd="'ng-format-negative'"
ng:class-even="'ng-input-indicator-wait'">
{{name}} &nbsp; &nbsp; &nbsp;
</span>
</li>
</ol>
*
* @scenario
it('should check ng:class-odd and ng:class-even', function(){
expect(element('.doc-example-live li:first span').attr('className')).
toMatch(/ng-format-negative/);
expect(element('.doc-example-live li:last span').attr('className')).
toMatch(/ng-input-indicator-wait/);
});
*/
angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
/**
* @ngdoc directive
* @name angular.directive.ng:show
*
* @description
* The `ng:show` and `ng:hide` allows you to show or hide a portion
* of the HTML conditionally.
*
* @element ANY
* @param {expression} expression if truthy then the element is
* shown or hidden respectively.
*
* @exampleDescription
* @example
Click me: <input type="checkbox" name="checked"><br/>
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
*
* @scenario
it('should check ng:show / ng:hide', function(){
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
input('checked').check();
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
});
*/
angularDirective("ng:show", function(expression, element){
return function(element){
this.$onEval(function(){
@@ -283,6 +820,35 @@ angularDirective("ng:show", function(expression, element){
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:hide
*
* @description
* The `ng:show` and `ng:hide` allows you to show or hide a portion
* of the HTML conditionally.
*
* @element ANY
* @param {expression} expression if truthy then the element is
* shown or hidden respectively.
*
* @exampleDescription
* @example
Click me: <input type="checkbox" name="checked"><br/>
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
*
* @scenario
it('should check ng:show / ng:hide', function(){
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
input('checked').check();
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
});
*/
angularDirective("ng:hide", function(expression, element){
return function(element){
this.$onEval(function(){
@@ -291,6 +857,22 @@ angularDirective("ng:hide", function(expression, element){
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:style
*
* @description
*
* @element ANY
* @param {expression} expression to eval.
*
* @exampleDescription
* @example
*
* @scenario
it('should check ng:style', function(){
});
*/
angularDirective("ng:style", function(expression, element){
return function(element){
var resetStyle = getStyle(element);
+42 -22
View File
@@ -23,8 +23,8 @@
it('should update', function(){
input('amount').enter('-1234');
expect(binding('amount | currency')).toBe('$-1,234.00');
// TODO: implement
// expect(binding('amount')).toHaveColor('red'); //what about toHaveCssClass instead?
expect(element('.doc-example-live .ng-binding').attr('className')).
toMatch(/ng-format-negative/);
});
*/
angularFilter.currency = function(amount){
@@ -47,17 +47,23 @@ angularFilter.currency = function(amount){
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
*
* @example
<span ng:non-bindable>{{1234.56789 | number}}</span>: {{1234.56789 | number}}<br/>
<span ng:non-bindable>{{1234.56789 | number:0}}</span>: {{1234.56789 | number:0}}<br/>
<span ng:non-bindable>{{1234.56789 | number:2}}</span>: {{1234.56789 | number:2}}<br/>
<span ng:non-bindable>{{-1234.56789 | number:4}}</span>: {{-1234.56789 | number:4}}
*
Enter number: <input name='val' value='1234.56789' /><br/>
Default formatting: {{val | number}}<br/>
No fractions: {{val | number:0}}<br/>
Negative number: {{-val | number:4}}
* @scenario
it('should format numbers', function(){
expect(binding('1234.56789 | number')).toBe('1,234.57');
expect(binding('1234.56789 | number:0')).toBe('1,235');
expect(binding('1234.56789 | number:2')).toBe('1,234.57');
expect(binding('-1234.56789 | number:4')).toBe('-1,234.5679');
expect(binding('val | number')).toBe('1,234.57');
expect(binding('val | number:0')).toBe('1,235');
expect(binding('-val | number:4')).toBe('-1,234.5679');
});
it('should update', function(){
input('val').enter('3374.333');
expect(binding('val | number')).toBe('3,374.33');
expect(binding('val | number:0')).toBe('3,374');
expect(binding('-val | number:4')).toBe('-3,374.3330');
});
*/
angularFilter.number = function(number, fractionSize){
@@ -168,7 +174,8 @@ var NUMBER_STRING = /^\d+$/;
* * `'a'`: am/pm marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-12001200)
*
* @param {(Date|number|string)} date Date to format either as Date object or milliseconds.
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
* @param {string=} format Formatting rules. If not specified, Date#toLocaleDateString is used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
*
@@ -188,13 +195,19 @@ var NUMBER_STRING = /^\d+$/;
*
*/
angularFilter.date = function(date, format) {
if (isString(date) && NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
if (isString(date)) {
if (NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
} else {
date = angularString.toDate(date);
}
}
if (isNumber(date)) {
date = new Date(date);
} else if (!(date instanceof Date)) {
}
if (!isDate(date)) {
return date;
}
@@ -231,12 +244,19 @@ angularFilter.date = function(date, format) {
*
* @css ng-monospace Always applied to the encapsulating element.
*
* @example
<span ng:non-bindable>{{ {a:1, b:[]} | json }}</span>: <pre>{{ {a:1, b:[]} | json }}</pre>
* @example:
<input type="text" name="objTxt" value="{a:1, b:[]}"
ng:eval="obj = $eval(objTxt)"/>
<pre>{{ obj | json }}</pre>
*
* @scenario
it('should jsonify filtered objects', function() {
expect(binding('{{ {a:1, b:[]} | json')).toBe('{\n "a":1,\n "b":[]}');
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
});
it('should update', function() {
input('objTxt').enter('[1, 2, 3]');
expect(binding('obj | json')).toBe('[1,2,3]');
});
*
*/
@@ -343,7 +363,7 @@ snippet&lt;/p&gt;</textarea>
});
it('should update', function(){
textarea('snippet').enter('new <b>text</b>');
input('snippet').enter('new <b>text</b>');
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
expect(using('#escaped-html').binding('snippet')).toBe("new &lt;b&gt;text&lt;/b&gt;");
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
@@ -394,8 +414,8 @@ and one more: ftp://127.0.0.1/.</textarea>
<td><div ng:bind="snippet"></div></td>
</tr>
</table>
*
* @scenario
@scenario
it('should linkify the snippet with urls', function(){
expect(using('#linky-filter').binding('snippet | linky')).
toBe('Pretty text with some links:\n' +
@@ -415,7 +435,7 @@ and one more: ftp://127.0.0.1/.</textarea>
});
it('should update', function(){
textarea('snippet').enter('new http://link.');
input('snippet').enter('new http://link.');
expect(using('#linky-filter').binding('snippet | linky')).
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
+118
View File
@@ -6,8 +6,76 @@ function toString(obj) {
var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;
angularFormatter.noop = formatter(identity, identity);
/**
* @ngdoc formatter
* @name angular.formatter.json
*
* @description
* Formats the user input as JSON text.
*
* @returns {string} A JSON string representation of the model.
*
* @example
* <div ng:init="data={name:'misko', project:'angular'}">
* <input type="text" size='50' name="data" ng:format="json"/>
* <pre>data={{data}}</pre>
* </div>
*
* @scenario
* it('should format json', function(){
* expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}');
* input('data').enter('{}');
* expect(binding('data')).toEqual('data={\n }');
* });
*/
angularFormatter.json = formatter(toJson, fromJson);
/**
* @ngdoc formatter
* @name angular.formatter.boolean
*
* @description
* Use boolean formatter if you wish to store the data as boolean.
*
* @returns Convert to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
*
* @example
* Enter truthy text:
* <input type="text" name="value" ng:format="boolean" value="no"/>
* <input type="checkbox" name="value"/>
* <pre>value={{value}}</pre>
*
* @scenario
* it('should format boolean', function(){
* expect(binding('value')).toEqual('value=false');
* input('value').enter('truthy');
* expect(binding('value')).toEqual('value=true');
* });
*/
angularFormatter['boolean'] = formatter(toString, toBoolean);
/**
* @ngdoc formatter
* @name angular.formatter.number
*
* @description
* Use number formatter if you wish to convert the user entered string to a number.
*
* @returns parse string to number.
*
* @example
* Enter valid number:
* <input type="text" name="value" ng:format="number" value="1234"/>
* <pre>value={{value}}</pre>
*
* @scenario
* it('should format numbers', function(){
* expect(binding('value')).toEqual('value=1234');
* input('value').enter('5678');
* expect(binding('value')).toEqual('value=5678');
* });
*/
angularFormatter.number = formatter(toString, function(obj){
if (obj == _null || NUMBER.exec(obj)) {
return obj===_null || obj === '' ? _null : 1*obj;
@@ -16,6 +84,31 @@ angularFormatter.number = formatter(toString, function(obj){
}
});
/**
* @ngdoc formatter
* @name angular.formatter.list
*
* @description
* Use number formatter if you wish to convert the user entered string to a number.
*
* @returns parse string to number.
*
* @example
* Enter a list of items:
* <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
* <input type="text" name="value" ng:format="list"/>
* <pre>value={{value}}</pre>
*
* @scenario
* it('should format lists', function(){
* expect(binding('value')).toEqual('value=["chair","table"]');
* this.addFutureAction('change to XYZ', function($window, $document, done){
* $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
* done();
* });
* expect(binding('value')).toEqual('value=["a","b"]');
* });
*/
angularFormatter.list = formatter(
function(obj) { return obj ? obj.join(", ") : obj; },
function(value) {
@@ -28,6 +121,31 @@ angularFormatter.list = formatter(
}
);
/**
* @ngdoc formatter
* @name angular.formatter.trim
*
* @description
* Use trim formatter if you wish to trim extra spaces in user text.
*
* @returns {String} Trim excess leading and trailing space.
*
* @example
* Enter text with leading/trailing spaces:
* <input type="text" name="value" ng:format="trim" value=" book "/>
* <input type="text" name="value" ng:format="trim"/>
* <pre>value={{value|json}}</pre>
*
* @scenario
* it('should format trim', function(){
* expect(binding('value')).toEqual('value="book"');
* this.addFutureAction('change to XYZ', function($window, $document, done){
* $document.elements('.doc-example :input:last').val(' text ').trigger('change');
* done();
* });
* expect(binding('value')).toEqual('value="text"');
* });
*/
angularFormatter.trim = formatter(
function(obj) { return obj ? trim("" + obj) : ""; }
);
+6 -6
View File
@@ -26,7 +26,7 @@ var OPERATORS = {
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
function lex(text, parseStringsForObjects){
var dateParseLength = parseStringsForObjects ? 20 : -1,
var dateParseLength = parseStringsForObjects ? 24 : -1,
tokens = [],
token,
index = 0,
@@ -101,26 +101,26 @@ function lex(text, parseStringsForObjects){
'_' == ch || ch == '$';
}
function isExpOperator(ch) {
return ch == '-' || ch == '+';
return ch == '-' || ch == '+' || isNumber(ch);
}
function readNumber() {
var number = "";
var start = index;
while (index < text.length) {
var ch = text.charAt(index);
var ch = lowercase(text.charAt(index));
if (ch == '.' || isNumber(ch)) {
number += ch;
} else {
var peekCh = peek();
if (ch == 'E' && isExpOperator(peekCh)) {
if (ch == 'e' && isExpOperator(peekCh)) {
number += ch;
} else if (isExpOperator(ch) &&
peekCh && isNumber(peekCh) &&
number.charAt(number.length - 1) == 'E') {
number.charAt(number.length - 1) == 'e') {
number += ch;
} else if (isExpOperator(ch) &&
(!peekCh || !isNumber(peekCh)) &&
number.charAt(number.length - 1) == 'E') {
number.charAt(number.length - 1) == 'e') {
throw 'Lexer found invalid exponential value "' + text + '"';
} else {
break;
+28
View File
@@ -294,3 +294,31 @@ function browserTrigger(element, type) {
};
})(_jQuery.fn);
/**
* Finds all bindings with the substring match of name and returns an
* array of their values.
*
* @param {string} name The name to match
* @return {Array.<string>} String of binding values
*/
_jQuery.fn.bindings = function(name) {
function contains(text, value) {
return value instanceof RegExp ?
value.test(text) :
text && text.indexOf(value) >= 0;
}
var result = [];
this.find('.ng-binding:visible').each(function() {
var element = new _jQuery(this);
if (!angular.isDefined(name) ||
contains(element.attr('ng:bind'), name) ||
contains(element.attr('ng:bind-template'), name)) {
if (element.is('input, textarea')) {
result.push(element.val());
} else {
result.push(element.html());
}
}
});
return result;
};
+1
View File
@@ -111,6 +111,7 @@ angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior,
$document.elements = function(selector) {
var args = Array.prototype.slice.call(arguments, 1);
selector = (self.selector || '') + ' ' + (selector || '');
selector = _jQuery.trim(selector) || '*';
angular.foreach(args, function(value, index) {
selector = selector.replace('$' + (index + 1), value);
});
+28 -76
View File
@@ -152,30 +152,16 @@ angular.scenario.dsl('using', function() {
/**
* Usage:
* binding(name) returns the value of a binding
* binding(name) returns the value of the first matching binding
*/
angular.scenario.dsl('binding', function() {
function contains(text, value) {
return value instanceof RegExp ?
value.test(text) :
text && text.indexOf(value) >= 0;
}
return function(name) {
return this.addFutureAction("select binding '" + name + "'", function($window, $document, done) {
var elements = $document.elements('.ng-binding');
for ( var i = 0; i < elements.length; i++) {
var element = new elements.init(elements[i]);
if (contains(element.attr('ng:bind'), name) ||
contains(element.attr('ng:bind-template'), name)) {
if (element.is('input, textarea')) {
done(null, element.val());
} else {
done(null, element.html());
}
return;
}
var values = $document.elements().bindings(name);
if (!values.length) {
return done("Binding selector '" + name + "' did not match.");
}
done("Binding selector '" + name + "' did not match.");
done(null, values[0]);
});
};
});
@@ -191,7 +177,7 @@ angular.scenario.dsl('input', function() {
chain.enter = function(value) {
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
var input = $document.elements('input[name="$1"]', this.name);
var input = $document.elements(':input[name="$1"]', this.name);
input.val(value);
input.trigger('change');
done();
@@ -200,7 +186,7 @@ angular.scenario.dsl('input', function() {
chain.check = function() {
return this.addFutureAction("checkbox '" + this.name + "' toggle", function($window, $document, done) {
var input = $document.elements('input:checkbox[name="$1"]', this.name);
var input = $document.elements(':checkbox[name="$1"]', this.name);
input.trigger('click');
done();
});
@@ -209,7 +195,7 @@ angular.scenario.dsl('input', function() {
chain.select = function(value) {
return this.addFutureAction("radio button '" + this.name + "' toggle '" + value + "'", function($window, $document, done) {
var input = $document.
elements('input:radio[name$="@$1"][value="$2"]', this.name, value);
elements(':radio[name$="@$1"][value="$2"]', this.name, value);
input.trigger('click');
done();
});
@@ -222,29 +208,6 @@ angular.scenario.dsl('input', function() {
});
/**
* Usage:
* textarea(name).enter(value) enters value in the text area with specified name
*/
angular.scenario.dsl('textarea', function() {
var chain = {};
chain.enter = function(value) {
return this.addFutureAction("textarea '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
var textarea = $document.elements('textarea[name="$1"]', this.name);
textarea.val(value);
textarea.trigger('change');
done();
});
};
return function(name) {
this.name = name;
return chain;
};
});
/**
* Usage:
* repeater('#products table', 'Product List').count() number of rows
@@ -266,16 +229,7 @@ angular.scenario.dsl('repeater', function() {
chain.column = function(binding) {
return this.addFutureAction("repeater '" + this.label + "' column '" + binding + "'", function($window, $document, done) {
var values = [];
$document.elements().each(function() {
_jQuery(this).find(':visible').each(function() {
var element = _jQuery(this);
if (element.attr('ng:bind') === binding) {
values.push(element.text());
}
});
});
done(null, values);
done(null, $document.elements().bindings(binding));
});
};
@@ -285,13 +239,7 @@ angular.scenario.dsl('repeater', function() {
var matches = $document.elements().slice(index, index + 1);
if (!matches.length)
return done('row ' + index + ' out of bounds');
_jQuery(matches[0]).find(':visible').each(function() {
var element = _jQuery(this);
if (angular.isDefined(element.attr('ng:bind'))) {
values.push(element.text());
}
});
done(null, values);
done(null, matches.bindings());
});
};
@@ -338,13 +286,14 @@ angular.scenario.dsl('select', function() {
* Usage:
* element(selector, label).count() get the number of elements that match selector
* element(selector, label).click() clicks an element
* element(selector, label).attr(name) gets the value of an attribute
* element(selector, label).attr(name, value) sets the value of an attribute
* element(selector, label).val() gets the value (as defined by jQuery)
* element(selector, label).val(value) sets the value (as defined by jQuery)
* element(selector, label).query(fn) executes fn(selectedElements, done)
* element(selector, label).{method}() gets the value (as defined by jQuery, ex. val)
* element(selector, label).{method}(value) sets the value (as defined by jQuery, ex. val)
* element(selector, label).{method}(key) gets the value (as defined by jQuery, ex. attr)
* element(selector, label).{method}(key, value) sets the value (as defined by jQuery, ex. attr)
*/
angular.scenario.dsl('element', function() {
var KEY_VALUE_METHODS = ['attr', 'css'];
var VALUE_METHODS = [
'val', 'text', 'html', 'height', 'innerHeight', 'outerHeight', 'width',
'innerWidth', 'outerWidth', 'position', 'scrollLeft', 'scrollTop', 'offset'
@@ -376,22 +325,25 @@ angular.scenario.dsl('element', function() {
});
};
chain.attr = function(name, value) {
var futureName = "element '" + this.label + "' get attribute '" + name + "'";
if (angular.isDefined(value)) {
futureName = "element '" + this.label + "' set attribute '" + name + "' to " + "'" + value + "'";
}
return this.addFutureAction(futureName, function($window, $document, done) {
done(null, $document.elements().attr(name, value));
});
};
chain.query = function(fn) {
return this.addFutureAction('element ' + this.label + ' custom query', function($window, $document, done) {
fn.call(this, $document.elements(), done);
});
};
angular.foreach(KEY_VALUE_METHODS, function(methodName) {
chain[methodName] = function(name, value) {
var futureName = "element '" + this.label + "' get " + methodName + " '" + name + "'";
if (angular.isDefined(value)) {
futureName = "element '" + this.label + "' set " + methodName + " '" + name + "' to " + "'" + value + "'";
}
return this.addFutureAction(futureName, function($window, $document, done) {
var element = $document.elements();
done(null, element[methodName].call(element, name, value));
});
};
});
angular.foreach(VALUE_METHODS, function(methodName) {
chain[methodName] = function(value) {
var futureName = "element '" + this.label + "' " + methodName;
+274 -9
View File
@@ -1,6 +1,31 @@
foreach({
extend(angularValidator, {
'noop': function() { return _null; },
/**
* @ngdoc validator
* @name angular.validator.regexp
* @description
* Use regexp validator to restrict the input to any Regular Expression.
*
* @param {string} value value to validate
* @param {regexp} expression regular expression.
* @css ng-validation-error
*
* @example
* Enter valid SSN:
* <input name="ssn" value="123-45-6789" ng:validate="regexp:/^\d\d\d-\d\d-\d\d\d\d$/" >
*
* @scenario
* it('should invalidate non ssn', function(){
* var textBox = element('.doc-example :input');
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
* expect(textBox.val()).toEqual('123-45-6789');
*
* input('ssn').enter('123-45-67890');
* expect(textBox.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'regexp': function(value, regexp, msg) {
if (!value.match(regexp)) {
return msg ||
@@ -10,6 +35,43 @@ foreach({
}
},
/**
* @ngdoc validator
* @name angular.validator.number
* @description
* Use number validator to restrict the input to numbers with an
* optional range. (See integer for whole numbers validator).
*
* @param {string} value value to validate
* @param {int=} [min=MIN_INT] minimum value.
* @param {int=} [max=MAX_INT] maximum value.
* @css ng-validation-error
*
* @example
* Enter number: <input name="n1" ng:validate="number" > <br>
* Enter number greater than 10: <input name="n2" ng:validate="number:10" > <br>
* Enter number between 100 and 200: <input name="n3" ng:validate="number:100:200" > <br>
*
* @scenario
* it('should invalidate number', function(){
* var n1 = element('.doc-example :input[name=n1]');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('n1').enter('1.x');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
*
* var n2 = element('.doc-example :input[name=n2]');
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
* input('n2').enter('9');
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
*
* var n3 = element('.doc-example :input[name=n3]');
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
* input('n3').enter('201');
* expect(n3.attr('className')).toMatch(/ng-validation-error/);
*
* });
*
*/
'number': function(value, min, max) {
var num = 1 * value;
if (num == value) {
@@ -25,6 +87,42 @@ foreach({
}
},
/**
* @ngdoc validator
* @name angular.validator.integer
* @description
* Use number validator to restrict the input to integers with an
* optional range. (See integer for whole numbers validator).
*
* @param {string} value value to validate
* @param {int=} [min=MIN_INT] minimum value.
* @param {int=} [max=MAX_INT] maximum value.
* @css ng-validation-error
*
* @example
* Enter integer: <input name="n1" ng:validate="integer" > <br>
* Enter integer equal or greater than 10: <input name="n2" ng:validate="integer:10" > <br>
* Enter integer between 100 and 200 (inclusive): <input name="n3" ng:validate="integer:100:200" > <br>
*
* @scenario
* it('should invalidate integer', function(){
* var n1 = element('.doc-example :input[name=n1]');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('n1').enter('1.1');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
*
* var n2 = element('.doc-example :input[name=n2]');
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
* input('n2').enter('10.1');
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
*
* var n3 = element('.doc-example :input[name=n3]');
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
* input('n3').enter('100.1');
* expect(n3.attr('className')).toMatch(/ng-validation-error/);
*
* });
*/
'integer': function(value, min, max) {
var numberError = angularValidator['number'](value, min, max);
if (numberError) return numberError;
@@ -34,6 +132,29 @@ foreach({
return _null;
},
/**
* @ngdoc validator
* @name angular.validator.date
* @description
* Use date validator to restrict the user input to a valid date
* in format in format MM/DD/YYYY.
*
* @param {string} value value to validate
* @css ng-validation-error
*
* @example
* Enter valid date:
* <input name="text" value="1/1/2009" ng:validate="date" >
*
* @scenario
* it('should invalidate date', function(){
* var n1 = element('.doc-example :input');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('text').enter('123/123/123');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'date': function(value) {
var fields = /^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/.exec(value);
var date = fields ? new Date(fields[3], fields[1]-1, fields[2]) : 0;
@@ -44,13 +165,28 @@ foreach({
_null : "Value is not a date. (Expecting format: 12/31/2009).";
},
'ssn': function(value) {
if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
return _null;
}
return "SSN needs to be in 999-99-9999 format.";
},
/**
* @ngdoc validator
* @name angular.validator.email
* @description
* Use email validator if you wist to restrict the user input to a valid email.
*
* @param {string} value value to validate
* @css ng-validation-error
*
* @example
* Enter valid email:
* <input name="text" ng:validate="email" value="me@example.com">
*
* @scenario
* it('should invalidate email', function(){
* var n1 = element('.doc-example :input');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('text').enter('a@b.c');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'email': function(value) {
if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
return _null;
@@ -58,6 +194,28 @@ foreach({
return "Email needs to be in username@host.com format.";
},
/**
* @ngdoc validator
* @name angular.validator.phone
* @description
* Use phone validator to restrict the input phone numbers.
*
* @param {string} value value to validate
* @css ng-validation-error
*
* @example
* Enter valid phone number:
* <input name="text" value="1(234)567-8901" ng:validate="phone" >
*
* @scenario
* it('should invalidate phone', function(){
* var n1 = element('.doc-example :input');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('text').enter('+12345678');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'phone': function(value) {
if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
return _null;
@@ -68,6 +226,28 @@ foreach({
return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
},
/**
* @ngdoc validator
* @name angular.validator.url
* @description
* Use phone validator to restrict the input URLs.
*
* @param {string} value value to validate
* @css ng-validation-error
*
* @example
* Enter valid phone number:
* <input name="text" value="http://example.com/abc.html" size="40" ng:validate="url" >
*
* @scenario
* it('should invalidate url', function(){
* var n1 = element('.doc-example :input');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('text').enter('abc://server/path');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'url': function(value) {
if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
return _null;
@@ -75,6 +255,29 @@ foreach({
return "URL needs to be in http://server[:port]/path format.";
},
/**
* @ngdoc validator
* @name angular.validator.json
* @description
* Use json validator if you wish to restrict the user input to a valid JSON.
*
* @param {string} value value to validate
* @css ng-validation-error
*
* @example
* <textarea name="json" cols="60" rows="5" ng:validate="json">
* {name:'abc'}
* </textarea>
*
* @scenario
* it('should invalidate json', function(){
* var n1 = element('.doc-example :input');
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('json').enter('{name}');
* expect(n1.attr('className')).toMatch(/ng-validation-error/);
* });
*
*/
'json': function(value) {
try {
fromJson(value);
@@ -84,6 +287,68 @@ foreach({
}
},
/**
* @ngdoc validator
* @name angular.validator.asynchronous
* @description
* Use asynchronous validator if the validation can not be computed
* immediately, but is provided through a callback. The widget
* automatically shows a spinning indicator while the validity of
* the widget is computed. This validator caches the result.
*
* @param {string} value value to validate
* @param {function(inputToValidate,validationDone)} validate function to call to validate the state
* of the input.
* @param {function(data)=} [update=noop] function to call when state of the
* validator changes
*
* @paramDescription
* The `validate` function (specified by you) is called as
* `validate(inputToValidate, validationDone)`:
*
* * `inputToValidate`: value of the input box.
* * `validationDone`: `function(error, data){...}`
* * `error`: error text to display if validation fails
* * `data`: data object to pass to update function
*
* The `update` function is optionally specified by you and is
* called by <angular/> on input change. Since the
* asynchronous validator caches the results, the update
* function can be called without a call to `validate`
* function. The function is called as `update(data)`:
*
* * `data`: data object as passed from validate function
*
* @css ng-input-indicator-wait, ng-validation-error
*
* @example
* <script>
* function myValidator(inputToValidate, validationDone) {
* setTimeout(function(){
* validationDone(inputToValidate.length % 2);
* }, 500);
* }
* </script>
* This input is validated asynchronously:
* <input name="text" ng:validate="asynchronous:$window.myValidator">
*
* @scenario
* it('should change color in delayed way', function(){
* var textBox = element('.doc-example :input');
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
*
* input('text').enter('X');
* expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/);
*
* pause(.6);
*
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
* expect(textBox.attr('className')).toMatch(/ng-validation-error/);
*
* });
*
*/
/*
* cache is attached to the element
* cache: {
@@ -132,4 +397,4 @@ foreach({
return inputState.error;
}
}, function(v,k) {angularValidator[k] = v;});
});
+226 -15
View File
@@ -1,5 +1,134 @@
/**
* @ngdoc widget
* @name angular.widget.HTML
*
* @description
* The most common widgets you will use will be in the from of the
* standard HTML set. These widgets are bound using the name attribute
* to an expression. In addition they can have `ng:validate`, `ng:required`,
* `ng:format`, `ng:change` attribute to further control their behavior.
*
* @usageContent
* see example below for usage
*
* <input type="text|checkbox|..." ... />
* <textarea ... />
* <select ...>
* <option>...</option>
* </select>
*
* @example
<table style="font-size:.9em;">
<tr>
<th>Name</th>
<th>Format</th>
<th>HTML</th>
<th>UI</th>
<th ng:non-bindable>{{input#}}</th>
</tr>
<tr>
<th>text</th>
<td>String</td>
<td><tt>&lt;input type="text" name="input1"&gt;</tt></td>
<td><input type="text" name="input1" size="4"></td>
<td><tt>{{input1|json}}</tt></td>
</tr>
<tr>
<th>textarea</th>
<td>String</td>
<td><tt>&lt;textarea name="input2"&gt;&lt;/textarea&gt;</tt></td>
<td><textarea name="input2" cols='6'></textarea></td>
<td><tt>{{input2|json}}</tt></td>
</tr>
<tr>
<th>radio</th>
<td>String</td>
<td><tt>
&lt;input type="radio" name="input3" value="A"&gt;<br>
&lt;input type="radio" name="input3" value="B"&gt;
</tt></td>
<td>
<input type="radio" name="input3" value="A">
<input type="radio" name="input3" value="B">
</td>
<td><tt>{{input3|json}}</tt></td>
</tr>
<tr>
<th>checkbox</th>
<td>Boolean</td>
<td><tt>&lt;input type="checkbox" name="input4" value="checked"&gt;</tt></td>
<td><input type="checkbox" name="input4" value="checked"></td>
<td><tt>{{input4|json}}</tt></td>
</tr>
<tr>
<th>pulldown</th>
<td>String</td>
<td><tt>
&lt;select name="input5"&gt;<br>
&nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select name="input5">
<option value="c">C</option>
<option value="d">D</option>
</select>
</td>
<td><tt>{{input5|json}}</tt></td>
</tr>
<tr>
<th>multiselect</th>
<td>Array</td>
<td><tt>
&lt;select name="input6" multiple size="4"&gt;<br>
&nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select name="input6" multiple size="4">
<option value="e">E</option>
<option value="f">F</option>
</select>
</td>
<td><tt>{{input6|json}}</tt></td>
</tr>
</table>
* @scenario
* it('should exercise text', function(){
* input('input1').enter('Carlos');
* expect(binding('input1')).toEqual('"Carlos"');
* });
* it('should exercise textarea', function(){
* input('input2').enter('Carlos');
* expect(binding('input2')).toEqual('"Carlos"');
* });
* it('should exercise radio', function(){
* expect(binding('input3')).toEqual('null');
* input('input3').select('A');
* expect(binding('input3')).toEqual('"A"');
* input('input3').select('B');
* expect(binding('input3')).toEqual('"B"');
* });
* it('should exercise checkbox', function(){
* expect(binding('input4')).toEqual('false');
* input('input4').check();
* expect(binding('input4')).toEqual('true');
* });
* it('should exercise pulldown', function(){
* expect(binding('input5')).toEqual('"c"');
* select('input5').option('d');
* expect(binding('input5')).toEqual('"d"');
* });
* it('should exercise multiselect', function(){
* expect(binding('input6')).toEqual('[]');
* select('input6').options('e');
* expect(binding('input6')).toEqual('["e"]');
* select('input6').options('e', 'f');
* expect(binding('input6')).toEqual('["e","f"]');
* });
*/
function modelAccessor(scope, element) {
@@ -244,15 +373,41 @@ angularWidget('option', function(){
});
/*ng:doc
* @type widget
* @name ng:include
/**
* @ngdoc widget
* @name angular.widget.ng:include
*
* @description
* Include external HTML fragment.
*
* Keep in mind that Same Origin Policy applies to included resources
* (e.g. ng:include won't work for file:// access).
*
* @param {string} src expression evaluating to URL.
* @param {Scope=} [scope=new_child_scope] expression evaluating to angular.scope
*
* @example
* <select name="url">
* <option value="angular.filter.date.html">date filter</option>
* <option value="angular.filter.html.html">html filter</option>
* <option value="">(blank)</option>
* </select>
* <tt>url = <a href="{{url}}">{{url}}</a></tt>
* <hr/>
* <ng:include src="url"></ng:include>
*
* @scenario
* it('should load date filter', function(){
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.date/);
* });
* it('should change to hmtl filter', function(){
* select('url').option('angular.filter.html.html');
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.html/);
* });
* it('should change to blank', function(){
* select('url').option('(blank)');
* expect(element('.doc-example ng\\:include').text()).toEqual('');
* });
*/
angularWidget('ng:include', function(element){
var compiler = this,
@@ -299,6 +454,55 @@ angularWidget('ng:include', function(element){
}
});
/**
* @ngdoc widget
* @name angular.widget.ng:switch
*
* @description
* Conditionally change the DOM structure.
*
* @usageContent
* <any ng:switch-when="matchValue1">...</any>
* <any ng:switch-when="matchValue2">...</any>
* ...
* <any ng:switch-default>...</any>
*
* @param {*} on expression to match against <tt>ng:switch-when</tt>.
* @paramDescription
* On child elments add:
*
* * `ng:switch-when`: the case statement to match against. If match then this
* case will be displayed.
* * `ng:switch-default`: the default case when no other casses match.
*
* @example
<select name="switch">
<option>settings</option>
<option>home</option>
<option>other</option>
</select>
<tt>switch={{switch}}</tt>
</hr>
<ng:switch on="switch" >
<div ng:switch-when="settings">Settings Div</div>
<span ng:switch-when="home">Home Span</span>
<span ng:switch-default>default</span>
</ng:switch>
</code>
*
* @scenario
* it('should start in settings', function(){
* expect(element('.doc-example ng\\:switch').text()).toEqual('Settings Div');
* });
* it('should change to home', function(){
* select('switch').option('home');
* expect(element('.doc-example ng\\:switch').text()).toEqual('Home Span');
* });
* it('should select deafault', function(){
* select('switch').option('other');
* expect(element('.doc-example ng\\:switch').text()).toEqual('default');
* });
*/
var ngSwitch = angularWidget('ng:switch', function (element){
var compiler = this,
watchExpr = element.attr("on"),
@@ -308,21 +512,26 @@ var ngSwitch = angularWidget('ng:switch', function (element){
changeExpr = element.attr('change') || '',
cases = [];
if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
if (!watchExpr) throw "Missing 'on' attribute.";
eachNode(element, function(caseElement){
var when = caseElement.attr('ng:switch-when');
if (when) {
cases.push({
when: function(scope, value){
var args = [value, when];
foreach(usingExprParams, function(arg){
args.push(arg);
});
return usingFn.apply(scope, args);
},
var switchCase = {
change: changeExpr,
element: caseElement,
template: compiler.compile(caseElement)
});
};
if (isString(when)) {
switchCase.when = function(scope, value){
var args = [value, when];
foreach(usingExprParams, function(arg){
args.push(arg);
});
return usingFn.apply(scope, args);
};
cases.unshift(switchCase);
} else if (isString(caseElement.attr('ng:switch-default'))) {
switchCase.when = valueFn(true);
cases.push(switchCase);
}
});
@@ -335,10 +544,12 @@ var ngSwitch = angularWidget('ng:switch', function (element){
return function(element){
var scope = this, childScope;
this.$watch(watchExpr, function(value){
var found = false;
element.html('');
childScope = createScope(scope);
foreach(cases, function(switchCase){
if (switchCase.when(childScope, value)) {
if (!found && switchCase.when(childScope, value)) {
found = true;
var caseElement = quickClone(switchCase.element);
element.append(caseElement);
childScope.$tryEval(switchCase.change, element);
@@ -353,7 +564,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
};
}, {
equals: function(on, when) {
return on == when;
return ''+on == when;
},
route: switchRouteMatcher
});
+87 -12
View File
@@ -26,8 +26,8 @@ describe("copy", function(){
it("should return same object", function (){
var obj = {};
var arr = [];
assertSame(obj, copy({}, obj));
assertSame(arr, copy([], arr));
expect(copy({}, obj)).toBe(obj);
expect(copy([], arr)).toBe(arr);
});
it("should copy Date", function(){
@@ -40,26 +40,26 @@ describe("copy", function(){
it("should copy array", function(){
var src = [1, {name:"value"}];
var dst = [{key:"v"}];
assertSame(dst, copy(src, dst));
assertEquals([1, {name:"value"}], dst);
assertEquals({name:"value"}, dst[1]);
assertNotSame(src[1], dst[1]);
expect(copy(src, dst)).toBe(dst);
expect(dst).toEqual([1, {name:"value"}]);
expect(dst[1]).toEqual({name:"value"});
expect(dst[1]).not.toBe(src[1]);
});
it('should copy empty array', function() {
var src = [];
var dst = [{key: "v"}];
assertEquals([], copy(src, dst));
assertEquals([], dst);
expect(copy(src, dst)).toEqual([]);
expect(dst).toEqual([]);
});
it("should copy object", function(){
var src = {a:{name:"value"}};
var dst = {b:{key:"v"}};
assertSame(dst, copy(src, dst));
assertEquals({a:{name:"value"}}, dst);
assertEquals(src.a, dst.a);
assertNotSame(src.a, dst.a);
expect(copy(src, dst)).toBe(dst);
expect(dst).toEqual({a:{name:"value"}});
expect(dst.a).toEqual(src.a);
expect(dst.a).not.toBe(src.a);
});
it("should copy primitives", function(){
@@ -255,6 +255,20 @@ describe('angularJsConfig', function() {
});
it('should extract angular autobind config from the script hashpath attributes', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
src: 'angularjs/angular.js#autobind'}];
}};
expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
autobind: true,
ie_compat: 'angularjs/angular-ie-compat.js',
ie_compat_id: 'ng-ie-compat'});
});
it("should default to versioned ie-compat file if angular file is versioned", function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
@@ -280,3 +294,64 @@ describe('angularJsConfig', function() {
ie_compat_id: 'ng-ie-compat'});
});
});
describe('extensionMap', function() {
it('should preserve $ properties on override', function() {
var extension = extensionMap({}, 'fake');
extension('first', {$one: true, $two: true});
var result = extension('first', {$one: false, $three: true});
expect(result.$one).toBeFalsy();
expect(result.$two).toBeTruthy();
expect(result.$three).toBeTruthy();
});
it('should not preserve non-angular properties', function() {
var extension = extensionMap({}, 'fake');
extension('first', {two: true});
var result = extension('first', {$one: false, $three: true});
expect(result.two).not.toBeDefined();
});
});
describe('angular service', function() {
it('should override services', function() {
var scope = createScope();
angular.service('fake', function() { return 'old'; });
angular.service('fake', function() { return 'new'; });
expect(scope.$inject('fake')).toEqual('new');
});
it('should preserve $ properties on override', function() {
angular.service('fake', {$one: true}, {$two: true});
var result = angular.service('fake', {$third: true});
expect(result.$one).toBeTruthy();
expect(result.$two).toBeTruthy();
expect(result.$third).toBeTruthy();
});
it('should not preserve non-angular properties on override', function() {
angular.service('fake', {one: true}, {two: true});
var result = angular.service('fake', {third: true});
expect(result.one).not.toBeDefined();
expect(result.two).not.toBeDefined();
expect(result.third).toBeTruthy();
});
});
describe('isDate', function() {
it('should return true for Date object', function() {
expect(isDate(new Date())).toBe(true);
});
it('should return false for non Date objects', function() {
expect(isDate([])).toBe(false);
expect(isDate('')).toBe(false);
expect(isDate(23)).toBe(false);
expect(isDate({})).toBe(false);
});
});
+22 -4
View File
@@ -185,18 +185,36 @@ describe('api', function(){
it('DateToUTC', function(){
var date = new Date("Sep 10 2003 13:02:03 GMT");
assertEquals("date", angular.Object.typeOf(date));
assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
assertEquals(date.getTime(), angular.String.toDate(angular.Date.toString(date)).getTime());
});
it('UTCtoDate', function(){
expect(angular.String.toDate("2003-09-10T13:02:03Z")).toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
//full ISO8061
expect(angular.String.toDate("2003-09-10T13:02:03.000Z")).
toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
//no millis
expect(angular.String.toDate("2003-09-10T13:02:03Z")).
toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
//no seconds
expect(angular.String.toDate("2003-09-10T13:02Z")).
toEqual(new Date("Sep 10 2003 13:02:00 GMT"));
//no minutes
expect(angular.String.toDate("2003-09-10T13Z")).
toEqual(new Date("Sep 10 2003 13:00:00 GMT"));
//no time
expect(angular.String.toDate("2003-09-10")).
toEqual(new Date("Sep 10 2003 00:00:00 GMT"));
});
it('StringFromUTC', function(){
var date = angular.String.toDate("2003-09-10T13:02:03Z");
var date = angular.String.toDate("2003-09-10T13:02:03.000Z");
assertEquals("date", angular.Object.typeOf(date));
assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
assertEquals("str", angular.String.toDate("str"));
});
+4 -4
View File
@@ -607,13 +607,13 @@ BinderTest.prototype.testItShouldListenOnRightScope = function() {
'<ul ng:init="counter=0; gCounter=0" ng:watch="w:counter=counter+1">' +
'<li ng:repeat="n in [1,2,4]" ng:watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
c.scope.$eval();
assertEquals(0, c.scope.$get("counter"));
assertEquals(0, c.scope.$get("gCounter"));
assertEquals(1, c.scope.$get("counter"));
assertEquals(7, c.scope.$get("gCounter"));
c.scope.$set("w", "something");
c.scope.$eval();
assertEquals(1, c.scope.$get("counter"));
assertEquals(7, c.scope.$get("gCounter"));
assertEquals(2, c.scope.$get("counter"));
assertEquals(14, c.scope.$get("gCounter"));
};
BinderTest.prototype.testItShouldRepeatOnHashes = function() {
+2 -1
View File
@@ -14,7 +14,8 @@ describe('compiler', function(){
watch: function(expression, element){
return function() {
this.$watch(expression, function(val){
log += ":" + val;
if (val)
log += ":" + val;
});
};
}
+75 -61
View File
@@ -1,91 +1,100 @@
describe('filter', function(){
describe('filter', function() {
var filter = angular.filter;
it('should called the filter when evaluating expression', function() {
var scope = createScope();
filter.fakeFilter = function(){};
spyOn(filter, 'fakeFilter');
scope.$eval('10|fakeFilter');
expect(filter.fakeFilter).toHaveBeenCalledWith(10);
delete filter['fakeFilter'];
});
it('should call filter on scope context', function() {
var scope = createScope();
scope.name = 'misko';
filter.fakeFilter = function() {
expect(this.name).toEqual('misko');
};
spyOn(filter, 'fakeFilter').andCallThrough();
scope.$eval('10|fakeFilter');
expect(filter.fakeFilter).toHaveBeenCalled();
delete filter['fakeFilter'];
});
describe('Currency', function(){
it('should do basic filter', function(){
describe('currency', function() {
it('should do basic filter', function() {
var html = jqLite('<span/>');
var context = {$element:html};
var currency = bind(context, filter.currency);
assertEquals(currency(0), '$0.00');
assertEquals(html.hasClass('ng-format-negative'), false);
assertEquals(currency(-999), '$-999.00');
assertEquals(html.hasClass('ng-format-negative'), true);
assertEquals(currency(1234.5678), '$1,234.57');
assertEquals(html.hasClass('ng-format-negative'), false);
expect(currency(0)).toEqual('$0.00');
expect(html.hasClass('ng-format-negative')).toBeFalsy();
expect(currency(-999)).toEqual('$-999.00');
expect(html.hasClass('ng-format-negative')).toBeTruthy();
expect(currency(1234.5678)).toEqual('$1,234.57');
expect(html.hasClass('ng-format-negative')).toBeFalsy();
});
});
describe('FilterThisIsContext', function(){
it('should do basic filter', function(){
expectAsserts(1);
var scope = createScope();
scope.name = 'misko';
filter.testFn = function () {
assertEquals('scope not equal', 'misko', this.name);
};
scope.$eval("0|testFn");
delete angular.filter['testFn'];
});
});
describe('NumberFormat', function(){
it('should do basic filter', function(){
describe('number', function() {
it('should do basic filter', function() {
var context = {jqElement:jqLite('<span/>')};
var number = bind(context, filter.number);
assertEquals('0', number(0, 0));
assertEquals('0.00', number(0));
assertEquals('-999.00', number(-999));
assertEquals('1,234.57', number(1234.5678));
assertEquals('', number(Number.NaN));
assertEquals('1,234.57', number("1234.5678"));
assertEquals("", number(1/0));
expect(number(0, 0)).toEqual('0');
expect(number(0)).toEqual('0.00');
expect(number(-999)).toEqual('-999.00');
expect(number(1234.5678)).toEqual('1,234.57');
expect(number(Number.NaN)).toEqual('');
expect(number("1234.5678")).toEqual('1,234.57');
expect(number(1/0)).toEqual("");
});
});
describe('Json', function () {
it('should do basic filter', function(){
assertEquals(toJson({a:"b"}, true), filter.json.call({$element:jqLite('<div></div>')}, {a:"b"}));
describe('json', function () {
it('should do basic filter', function() {
expect(filter.json.call({$element:jqLite('<div></div>')}, {a:"b"})).toEqual(toJson({a:"b"}, true));
});
});
describe('Lowercase', function() {
it('should do basic filter', function(){
assertEquals('abc', filter.lowercase('AbC'));
assertEquals(null, filter.lowercase(null));
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(){
assertEquals('ABC', filter.uppercase('AbC'));
assertEquals(null, filter.uppercase(null));
describe('uppercase', function() {
it('should do basic filter', function() {
expect(filter.uppercase('AbC')).toEqual('ABC');
expect(filter.uppercase(null)).toBeNull();
});
});
describe('Html', function() {
it('should do basic filter', function(){
describe('html', function() {
it('should do basic filter', function() {
var html = filter.html("a<b>c</b>d");
expect(html instanceof HTML).toBeTruthy();
expect(html.html).toEqual("a<b>c</b>d");
});
});
describe('Linky', function() {
describe('linky', function() {
var linky = filter.linky;
it('should do basic filter', function(){
assertEquals(
'<a href="http://ab/">http://ab/</a> ' +
'(<a href="http://a/">http://a/</a>) ' +
'&lt;<a href="http://a/">http://a/</a>&gt; ' +
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c',
linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html);
assertEquals(undefined, linky(undefined));
it('should do basic filter', function() {
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html).
toEqual('<a href="http://ab/">http://ab/</a> ' +
'(<a href="http://a/">http://a/</a>) ' +
'&lt;<a href="http://a/">http://a/</a>&gt; ' +
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c');
expect(linky(undefined)).not.toBeDefined();
});
it('should handle mailto:', function(){
it('should handle mailto:', function() {
expect(linky("mailto:me@example.com").html).toEqual('<a href="mailto:me@example.com">me@example.com</a>');
expect(linky("me@example.com").html).toEqual('<a href="mailto:me@example.com">me@example.com</a>');
expect(linky("send email to me@example.com, but").html).
@@ -95,13 +104,12 @@ describe('filter', function(){
describe('date', function(){
var morning = new TzDate(+5, '2010-09-03T12:05:08Z'); //7am
var noon = new TzDate(+5, '2010-09-03T17:05:08Z'); //12pm
var midnight = new TzDate(+5, '2010-09-03T05:05:08Z'); //12am
var morning = new TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
var noon = new TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
var midnight = new TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
it('should ignore falsy inputs', function() {
expect(filter.date(null)).toEqual(null);
expect(filter.date(null)).toBeNull();
expect(filter.date('')).toEqual('');
});
@@ -115,7 +123,7 @@ describe('filter', function(){
expect(filter.date(noon.getTime() + "")).toEqual(noon.toLocaleDateString());
});
it('should accept format', function() {
it('should accept various format strings', function() {
expect(filter.date(morning, "yy-MM-dd HH:mm:ss")).
toEqual('10-09-03 07:05:08');
@@ -128,5 +136,11 @@ describe('filter', function(){
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
toEqual('2010-09-03 12=12:05:08pm0500');
});
it('should be able to parse ISO 8601 dates/times using', function() {
var isoString = '2010-09-03T05:05:08.872Z';
expect(filter.date(isoString)).
toEqual(angular.String.toDate(isoString).toLocaleDateString());
});
});
});
+71 -65
View File
@@ -1,107 +1,107 @@
describe('json', function(){
it('should parse Primitives', function() {
assertEquals("null", toJson(0/0));
assertEquals("null", toJson(null));
assertEquals("true", toJson(true));
assertEquals("false", toJson(false));
assertEquals("123.45", toJson(123.45));
assertEquals('"abc"', toJson("abc"));
assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
it('should serialize primitives', function() {
expect(toJson(0/0)).toEqual('null');
expect(toJson(null)).toEqual('null');
expect(toJson(true)).toEqual('true');
expect(toJson(false)).toEqual('false');
expect(toJson(123.45)).toEqual("123.45");
expect(toJson("abc")).toEqual('"abc"');
expect(toJson("a \t \n \r b \\")).toEqual('"a \\t \\n \\r b \\\\"');
});
it('should parse Escaping', function() {
assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
it('should serialize strings with escaped characters', function() {
expect(toJson("7\\\"7")).toEqual("\"7\\\\\\\"7\"");
});
it('should parse Objects', function() {
assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
it('should serialize objects', function() {
expect(toJson({a:1,b:2})).toEqual('{"a":1,"b":2}');
expect(toJson({a:{b:2}})).toEqual('{"a":{"b":2}}');
expect(toJson({a:{b:{c:0}}})).toEqual('{"a":{"b":{"c":0}}}');
expect(toJson({a:{b:0/0}})).toEqual('{"a":{"b":null}}');
});
it('should parse ObjectPretty', function() {
assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
it('should format objects pretty', function() {
expect(toJson({a:1,b:2}, true)).toEqual('{\n "a":1,\n "b":2}');
expect(toJson({a:{b:2}}, true)).toEqual('{\n "a":{\n "b":2}}');
});
it('should parse Array', function() {
assertEquals('[]', toJson([]));
assertEquals('[1,"b"]', toJson([1,"b"]));
it('should serialize array', function() {
expect(toJson([])).toEqual('[]');
expect(toJson([1,"b"])).toEqual('[1,"b"]');
});
it('should parse RegExp', function() {
assertEquals('"/foo/"', toJson(/foo/));
assertEquals('[1,"/foo/"]', toJson([1,new RegExp("foo")]));
it('should serialize RegExp', function() {
expect(toJson(/foo/)).toEqual('"/foo/"');
expect(toJson([1,new RegExp("foo")])).toEqual('[1,"/foo/"]');
});
it('should parse IgnoreFunctions', function() {
assertEquals('[null,1]', toJson([function(){},1]));
assertEquals('{}', toJson({a:function(){}}));
it('should ignore functions', function() {
expect(toJson([function(){},1])).toEqual('[null,1]');
expect(toJson({a:function(){}})).toEqual('{}');
});
it('should parse ParseNull', function() {
assertNull(fromJson("null"));
it('should parse null', function() {
expect(fromJson("null")).toBeNull();
});
it('should parse ParseBoolean', function() {
assertTrue(fromJson("true"));
assertFalse(fromJson("false"));
it('should parse boolean', function() {
expect(fromJson("true")).toBeTruthy();
expect(fromJson("false")).toBeFalsy();
});
it('should parse $$isIgnored', function() {
assertEquals("{}", toJson({$$:0}));
});
it('should parse ArrayWithEmptyItems', function() {
it('should serialize array with empty items', function() {
var a = [];
a[1] = "X";
assertEquals('[null,"X"]', toJson(a));
expect(toJson(a)).toEqual('[null,"X"]');
});
it('should parse ItShouldEscapeUnicode', function() {
assertEquals(1, "\u00a0".length);
assertEquals(8, toJson("\u00a0").length);
assertEquals(1, fromJson(toJson("\u00a0")).length);
it('should escape unicode', function() {
expect("\u00a0".length).toEqual(1);
expect(toJson("\u00a0").length).toEqual(8);
expect(fromJson(toJson("\u00a0")).length).toEqual(1);
});
it('should parse ItShouldUTCDates', function() {
var date = angular.String.toDate("2009-10-09T01:02:03Z");
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
assertEquals(date.getTime(),
fromJson('"2009-10-09T01:02:03Z"').getTime());
it('should serialize UTC dates', function() {
var date = angular.String.toDate("2009-10-09T01:02:03.027Z");
expect(toJson(date)).toEqual('"2009-10-09T01:02:03.027Z"');
expect(fromJson('"2009-10-09T01:02:03.027Z"').getTime()).toEqual(date.getTime());
});
it('should parse ItShouldPreventRecursion', function() {
it('should prevent recursion', function() {
var obj = {a:'b'};
obj.recursion = obj;
assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
expect(angular.toJson(obj)).toEqual('{"a":"b","recursion":RECURSION}');
});
it('should parse ItShouldIgnore$Properties', function() {
var scope = createScope();
scope.a = 'a';
scope['$b'] = '$b';
scope.c = 'c';
expect(angular.toJson(scope)).toEqual('{"a":"a","c":"c","this":RECURSION}');
it('should serialize $ properties', function() {
var obj = {$a: 'a'}
expect(angular.toJson(obj)).toEqual('{"$a":"a"}');
});
it('should parse ItShouldSerializeInheritedProperties', function() {
var scope = createScope({p:'p'});
scope.a = 'a';
expect(angular.toJson(scope)).toEqual('{"a":"a","p":"p","this":RECURSION}');
it('should serialize inherited properties', function() {
var obj = inherit({p:'p'});
obj.a = 'a';
expect(angular.toJson(obj)).toEqual('{"a":"a","p":"p"}');
});
it('should parse ItShouldSerializeSameObjectsMultipleTimes', function() {
it('should serialize same objects multiple times', function() {
var obj = {a:'b'};
assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
expect(angular.toJson({A:obj, B:obj})).toEqual('{"A":{"a":"b"},"B":{"a":"b"}}');
});
it('should parse ItShouldNotSerializeUndefinedValues', function() {
assertEquals('{}', angular.toJson({A:undefined}));
it('should not serialize undefined values', function() {
expect(angular.toJson({A:undefined})).toEqual('{}');
});
it('should not serialize $window object', function() {
expect(toJson(window)).toEqual('WINDOW');
});
it('should not serialize $document object', function() {
expect(toJson(document)).toEqual('DOCUMENT');
});
it('should parse ItShouldParseFloats', function() {
it('should parse floats', function() {
expect(fromJson("{value:2.55, name:'misko'}")).toEqual({value:2.55, name:'misko'});
});
@@ -109,6 +109,13 @@ describe('json', function(){
expect(fromJson("{neg:-2.55, pos:+.3, a:[-2, +.1, -.2, +.3]}")).toEqual({neg:-2.55, pos:+.3, a:[-2, +.1, -.2, +.3]});
});
it('should parse exponents', function() {
expect(fromJson("{exp:1.2E10}")).toEqual({exp:1.2E10});
expect(fromJson("{exp:1.2E-10}")).toEqual({exp:1.2E-10});
expect(fromJson("{exp:1.2e+10}")).toEqual({exp:1.2E10});
expect(fromJson("{exp:1.2e-10}")).toEqual({exp:1.2E-10});
});
describe('security', function(){
it('should not allow naked expressions', function(){
expect(function(){fromJson('1+2');}).toThrow("Did not understand '+2' while evaluating '1+2'.");
@@ -140,4 +147,3 @@ describe('json', function(){
});
});
+226 -266
View File
@@ -1,146 +1,144 @@
describe('parser', function(){
describe('lexer', function(){
it('should TokenizeAString', function(){
describe('parser', function() {
describe('lexer', function() {
it('should tokenize a string', function() {
var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
var i = 0;
assertEquals(tokens[i].index, 0);
assertEquals(tokens[i].text, 'a.bc');
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('a.bc');
i++;
assertEquals(tokens[i].index, 4);
assertEquals(tokens[i].text, '[');
expect(tokens[i].index).toEqual(4);
expect(tokens[i].text).toEqual('[');
i++;
assertEquals(tokens[i].index, 5);
assertEquals(tokens[i].text, 22);
expect(tokens[i].index).toEqual(5);
expect(tokens[i].text).toEqual(22);
i++;
assertEquals(tokens[i].index, 7);
assertEquals(tokens[i].text, ']');
expect(tokens[i].index).toEqual(7);
expect(tokens[i].text).toEqual(']');
i++;
assertEquals(tokens[i].index, 8);
assertEquals(tokens[i].text, '+');
expect(tokens[i].index).toEqual(8);
expect(tokens[i].text).toEqual('+');
i++;
assertEquals(tokens[i].index, 9);
assertEquals(tokens[i].text, 1.3);
expect(tokens[i].index).toEqual(9);
expect(tokens[i].text).toEqual(1.3);
i++;
assertEquals(tokens[i].index, 12);
assertEquals(tokens[i].text, '|');
expect(tokens[i].index).toEqual(12);
expect(tokens[i].text).toEqual('|');
i++;
assertEquals(tokens[i].index, 13);
assertEquals(tokens[i].text, 'f');
expect(tokens[i].index).toEqual(13);
expect(tokens[i].text).toEqual('f');
i++;
assertEquals(tokens[i].index, 14);
assertEquals(tokens[i].text, ':');
expect(tokens[i].index).toEqual(14);
expect(tokens[i].text).toEqual(':');
i++;
assertEquals(tokens[i].index, 15);
assertEquals(tokens[i].string, "a'c");
expect(tokens[i].index).toEqual(15);
expect(tokens[i].string).toEqual("a'c");
i++;
assertEquals(tokens[i].index, 21);
assertEquals(tokens[i].text, ':');
expect(tokens[i].index).toEqual(21);
expect(tokens[i].text).toEqual(':');
i++;
assertEquals(tokens[i].index, 22);
assertEquals(tokens[i].string, 'd"e');
expect(tokens[i].index).toEqual(22);
expect(tokens[i].string).toEqual('d"e');
});
it('should TokenizeUndefined', function(){
it('should tokenize undefined', function() {
var tokens = lex("undefined");
var i = 0;
assertEquals(tokens[i].index, 0);
assertEquals(tokens[i].text, 'undefined');
assertEquals(undefined, tokens[i].fn());
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('undefined');
expect(undefined).toEqual(tokens[i].fn());
});
it('should TokenizeRegExp', function(){
it('should tokenize RegExp', function() {
var tokens = lex("/r 1/");
var i = 0;
assertEquals(tokens[i].index, 0);
assertEquals(tokens[i].text, 'r 1');
assertEquals("r 1".match(tokens[i].fn())[0], 'r 1');
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('r 1');
expect("r 1".match(tokens[i].fn())[0]).toEqual('r 1');
});
it('should QuotedString', function(){
it('should tokenize quoted string', function() {
var str = "['\\'', \"\\\"\"]";
var tokens = lex(str);
assertEquals(1, tokens[1].index);
assertEquals("'", tokens[1].string);
expect(tokens[1].index).toEqual(1);
expect(tokens[1].string).toEqual("'");
assertEquals(7, tokens[3].index);
assertEquals('"', tokens[3].string);
expect(tokens[3].index).toEqual(7);
expect(tokens[3].string).toEqual('"');
});
it('should QuotedStringEscape', function(){
it('should tokenize escaped quoted string', function() {
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
var tokens = lex(str);
assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string);
expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0');
});
it('should TokenizeUnicode', function(){
it('should tokenize unicode', function() {
var tokens = lex('"\\u00A0"');
assertEquals(1, tokens.length);
assertEquals('\u00a0', tokens[0].string);
expect(tokens.length).toEqual(1);
expect(tokens[0].string).toEqual('\u00a0');
});
it('should TokenizeRegExpWithOptions', function(){
it('should tokenize RegExp with options', function() {
var tokens = lex("/r/g");
var i = 0;
assertEquals(tokens[i].index, 0);
assertEquals(tokens[i].text, 'r');
assertEquals(tokens[i].flags, 'g');
assertEquals("rr".match(tokens[i].fn()).length, 2);
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('r');
expect(tokens[i].flags).toEqual('g');
expect("rr".match(tokens[i].fn()).length).toEqual(2);
});
it('should TokenizeRegExpWithEscape', function(){
it('should tokenize RegExp with escaping', function() {
var tokens = lex("/\\/\\d/");
var i = 0;
assertEquals(tokens[i].index, 0);
assertEquals(tokens[i].text, '\\/\\d');
assertEquals("/1".match(tokens[i].fn())[0], '/1');
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('\\/\\d');
expect("/1".match(tokens[i].fn())[0]).toEqual('/1');
});
it('should IgnoreWhitespace', function(){
it('should ignore whitespace', function() {
var tokens = lex("a \t \n \r b");
assertEquals(tokens[0].text, 'a');
assertEquals(tokens[1].text, 'b');
expect(tokens[0].text).toEqual('a');
expect(tokens[1].text).toEqual('b');
});
it('should Relation', function(){
it('should tokenize relation', function() {
var tokens = lex("! == != < > <= >=");
assertEquals(tokens[0].text, '!');
assertEquals(tokens[1].text, '==');
assertEquals(tokens[2].text, '!=');
assertEquals(tokens[3].text, '<');
assertEquals(tokens[4].text, '>');
assertEquals(tokens[5].text, '<=');
assertEquals(tokens[6].text, '>=');
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('>=');
});
it('should Statements', function(){
it('should tokenize statements', function() {
var tokens = lex("a;b;");
assertEquals(tokens[0].text, 'a');
assertEquals(tokens[1].text, ';');
assertEquals(tokens[2].text, 'b');
assertEquals(tokens[3].text, ';');
expect(tokens[0].text).toEqual('a');
expect(tokens[1].text).toEqual(';');
expect(tokens[2].text).toEqual('b');
expect(tokens[3].text).toEqual(';');
});
it('should Number', function(){
it('should tokenize number', function() {
var tokens = lex("0.5");
expect(tokens[0].text).toEqual(0.5);
});
it('should NegativeNumber', function(){
it('should tokenize negative number', function() {
var value = createScope().$eval("-0.5");
expect(value).toEqual(-0.5);
@@ -148,7 +146,7 @@ describe('parser', function(){
expect(value).toEqual({a:-0.5});
});
it('should NumberExponent', function(){
it('should tokenize number with exponent', function() {
var tokens = lex("0.5E-10");
expect(tokens[0].text).toEqual(0.5E-10);
expect(createScope().$eval("0.5E-10")).toEqual(0.5E-10);
@@ -157,308 +155,270 @@ describe('parser', function(){
expect(tokens[0].text).toEqual(0.5E+10);
});
it('should NumberExponentInvalid', function(){
assertThrows('Lexer found invalid exponential value "0.5E-"', function(){
it('should throws exception for invalid exponent', function() {
expect(function() {
lex("0.5E-");
});
assertThrows('Lexer found invalid exponential value "0.5E-A"', function(){
}).toThrow('Lexer found invalid exponential value "0.5E-"');
expect(function() {
lex("0.5E-A");
});
}).toThrow('Lexer found invalid exponential value "0.5E-A"');
});
it('should NumberStartingWithDot', function(){
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(){
assertThrows("Lexer Error: Invalid unicode escape [\\u1''b] starting at column '0' in expression ''\\u1''bla''.", function(){
it('should throw error on invalid unicode', function() {
expect(function() {
lex("'\\u1''bla'");
});
}).toThrow("Lexer Error: Invalid unicode escape [\\u1''b] starting at column '0' in expression ''\\u1''bla''.");
});
});
var scope;
beforeEach(function () {
scope = createScope();
});
it('should parse Expressions', function(){
var scope = createScope();
assertEquals(scope.$eval("-1"), -1);
assertEquals(scope.$eval("1 + 2.5"), 3.5);
assertEquals(scope.$eval("1 + -2.5"), -1.5);
assertEquals(scope.$eval("1+2*3/4"), 1+2*3/4);
assertEquals(scope.$eval("0--1+1.5"), 0- -1 + 1.5);
assertEquals(scope.$eval("-0--1++2*-3/-4"), -0- -1+ +2*-3/-4);
assertEquals(scope.$eval("1/2*3"), 1/2*3);
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(){
var scope = createScope();
assertEquals(scope.$eval("false"), false);
assertEquals(scope.$eval("!true"), false);
assertEquals(scope.$eval("1==1"), true);
assertEquals(scope.$eval("1!=2"), true);
assertEquals(scope.$eval("1<2"), true);
assertEquals(scope.$eval("1<=1"), true);
assertEquals(scope.$eval("1>2"), 1>2);
assertEquals(scope.$eval("2>=1"), 2>=1);
assertEquals(true === 2<3, scope.$eval("true==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!=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);
});
it('should parse Logical', function(){
var scope = createScope();
assertEquals(scope.$eval("0&&2"), 0&&2);
assertEquals(scope.$eval("0||2"), 0||2);
assertEquals(scope.$eval("0||1&&2"), 0||1&&2);
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 String', function(){
var scope = createScope();
assertEquals(scope.$eval("'a' + 'b c'"), "ab c");
it('should parse string', function() {
expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
});
it('should parse Filters', function(){
it('should parse filters', function(){
angular.filter.substring = function(input, start, end) {
return input.substring(start, end);
};
angular.filter.upper = {_case:function(input) {
angular.filter.upper = {_case: function(input) {
return input.toUpperCase();
}};
var scope = createScope();
try {
expect(function() {
scope.$eval("1|nonExistant");
fail();
} catch (e) {
assertEquals(e, "Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
}
}).toThrow("Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
scope.$set('offset', 3);
assertEquals(scope.$eval("'abcd'|upper._case"), "ABCD");
assertEquals(scope.$eval("'abcd'|substring:1:offset"), "bc");
assertEquals(scope.$eval("'abcd'|substring:1:3|upper._case"), "BC");
expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD");
expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
expect(scope.$eval("'abcd'|substring:1:3|upper._case")).toEqual("BC");
});
it('should parse ScopeAccess', function(){
var scope = createScope();
it('should access scope', function() {
scope.$set('a', 123);
scope.$set('b.c', 456);
assertEquals(scope.$eval("a", scope), 123);
assertEquals(scope.$eval("b.c", scope), 456);
assertEquals(scope.$eval("x.y.z", scope), undefined);
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 parse Grouping', function(){
var scope = createScope();
assertEquals(scope.$eval("(1+2)*3"), (1+2)*3);
it('should evaluate grouped expressions', function() {
expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3);
});
it('should parse Assignments', function(){
var scope = createScope();
assertEquals(scope.$eval("a=12"), 12);
assertEquals(scope.$get("a"), 12);
it('should evaluate assignments', function() {
expect(scope.$eval("a=12")).toEqual(12);
expect(scope.$get("a")).toEqual(12);
scope = createScope();
assertEquals(scope.$eval("x.y.z=123;"), 123);
assertEquals(scope.$get("x.y.z"), 123);
expect(scope.$eval("x.y.z=123;")).toEqual(123);
expect(scope.$get("x.y.z")).toEqual(123);
assertEquals(234, scope.$eval("a=123; b=234"));
assertEquals(123, scope.$get("a"));
assertEquals(234, scope.$get("b"));
expect(scope.$eval("a=123; b=234")).toEqual(234);
expect(scope.$get("a")).toEqual(123);
expect(scope.$get("b")).toEqual(234);
});
it('should parse FunctionCallsNoArgs', function(){
var scope = createScope();
it('should evaluate function call without arguments', function() {
scope.$set('const', function(a,b){return 123;});
assertEquals(scope.$eval("const()"), 123);
expect(scope.$eval("const()")).toEqual(123);
});
it('should parse FunctionCalls', function(){
var scope = createScope();
scope.$set('add', function(a,b){
it('should evaluate function call with arguments', function() {
scope.$set('add', function(a,b) {
return a+b;
});
assertEquals(3, scope.$eval("add(1,2)"));
expect(scope.$eval("add(1,2)")).toEqual(3);
});
it('should parse CalculationBug', function(){
var scope = createScope();
it('should evaluate multiplication and division', function() {
scope.$set('taxRate', 8);
scope.$set('subTotal', 100);
assertEquals(scope.$eval("taxRate / 100 * subTotal"), 8);
assertEquals(scope.$eval("subTotal * taxRate / 100"), 8);
expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
});
it('should parse Array', function(){
var scope = createScope();
assertEquals(scope.$eval("[]").length, 0);
assertEquals(scope.$eval("[1, 2]").length, 2);
assertEquals(scope.$eval("[1, 2]")[0], 1);
assertEquals(scope.$eval("[1, 2]")[1], 2);
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 parse ArrayAccess', function(){
var scope = createScope();
assertEquals(scope.$eval("[1][0]"), 1);
assertEquals(scope.$eval("[[1]][0][0]"), 1);
assertEquals(scope.$eval("[].length"), 0);
assertEquals(scope.$eval("[1, 2].length"), 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 parse Object', function(){
var scope = createScope();
assertEquals(toJson(scope.$eval("{}")), "{}");
assertEquals(toJson(scope.$eval("{a:'b'}")), '{"a":"b"}');
assertEquals(toJson(scope.$eval("{'a':'b'}")), '{"a":"b"}');
assertEquals(toJson(scope.$eval("{\"a\":'b'}")), '{"a":"b"}');
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 parse ObjectAccess', function(){
var scope = createScope();
assertEquals("WC", scope.$eval("{false:'WC', true:'CC'}[false]"));
it('should evaluate object access', function() {
expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
});
it('should parse JSON', function(){
var scope = createScope();
assertEquals(toJson(scope.$eval("[{}]")), "[{}]");
assertEquals(toJson(scope.$eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]');
it('should evaluate JSON', function() {
expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]");
expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
});
it('should parse MultippleStatements', function(){
var scope = createScope();
assertEquals(scope.$eval("a=1;b=3;a+b"), 4);
assertEquals(scope.$eval(";;1;;"), 1);
it('should evaluate multipple statements', function() {
expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
expect(scope.$eval(";;1;;")).toEqual(1);
});
it('should parse ParseThrow', function(){
expectAsserts(1);
var scope = createScope();
it('should evaluate throw', function() {
scope.$set('e', 'abc');
try {
expect(function() {
scope.$eval("throw e");
} catch(e) {
assertEquals(e, 'abc');
}
}).toThrow('abc');
});
it('should parse MethodsGetDispatchedWithCorrectThis', function(){
var scope = createScope();
var C = function (){
this.a=123;
it('should evaluate object methods in correct context (this)', function() {
var C = function () {
this.a = 123;
};
C.prototype.getA = function(){
C.prototype.getA = function() {
return this.a;
};
scope.$set("obj", new C());
assertEquals(123, scope.$eval("obj.getA()"));
expect(scope.$eval("obj.getA()")).toEqual(123);
});
it('should parse MethodsArgumentsGetCorrectThis', function(){
var scope = createScope();
var C = function (){
this.a=123;
it('should evaluate methods in correct context (this) in argument', function() {
var C = function () {
this.a = 123;
};
C.prototype.sum = function(value){
C.prototype.sum = function(value) {
return this.a + value;
};
C.prototype.getA = function(){
C.prototype.getA = function() {
return this.a;
};
scope.$set("obj", new C());
assertEquals(246, scope.$eval("obj.sum(obj.getA())"));
expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
});
it('should parse ObjectPointsToScopeValue', function(){
var scope = createScope();
it('should evaluate objects on scope context', function() {
scope.$set('a', "abc");
assertEquals("abc", scope.$eval("{a:a}").a);
expect(scope.$eval("{a:a}").a).toEqual("abc");
});
it('should parse FieldAccess', function(){
var scope = createScope();
var fn = function(){
return {name:'misko'};
};
scope.$set('a', fn);
assertEquals("misko", scope.$eval("a().name"));
it('should evaluate field access on function call result', function() {
scope.$set('a', function() {
return {name:'misko'};
});
expect(scope.$eval("a().name")).toEqual("misko");
});
it('should parse ArrayIndexBug', function () {
var scope = createScope();
it('should evaluate field access after array access', function () {
scope.$set('items', [{}, {name:'misko'}]);
assertEquals("misko", scope.$eval('items[1].name'));
expect(scope.$eval('items[1].name')).toEqual("misko");
});
it('should parse ArrayAssignment', function () {
var scope = createScope();
it('should evaluate array assignment', function() {
scope.$set('items', []);
assertEquals("abc", scope.$eval('items[1] = "abc"'));
assertEquals("abc", scope.$eval('items[1]'));
expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
expect(scope.$eval('items[1]')).toEqual("abc");
// Dont know how to make this work....
// assertEquals("moby", scope.$eval('books[1] = "moby"'));
// assertEquals("moby", scope.$eval('books[1]'));
// expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
// expect(scope.$eval('books[1]')).toEqual("moby");
});
it('should parse FiltersCanBeGrouped', function () {
var scope = createScope({name:'MISKO'});
assertEquals('misko', scope.$eval('n = (name|lowercase)'));
assertEquals('misko', scope.$eval('n'));
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 parse Remainder', function () {
var scope = createScope();
assertEquals(1, scope.$eval('1%2'));
it('should evaluate remainder', function() {
expect(scope.$eval('1%2')).toEqual(1);
});
it('should parse SumOfUndefinedIsNotUndefined', function () {
var scope = createScope();
assertEquals(1, scope.$eval('1+undefined'));
assertEquals(1, scope.$eval('undefined+1'));
it('should evaluate sum with undefined', function() {
expect(scope.$eval('1+undefined')).toEqual(1);
expect(scope.$eval('undefined+1')).toEqual(1);
});
it('should parse MissingThrowsError', function(){
var scope = createScope();
try {
it('should throw exception on non-closed bracket', function() {
expect(function() {
scope.$eval('[].count(');
fail();
} catch (e) {
assertEquals('Unexpected end of expression: [].count(', e);
}
}).toThrow('Unexpected end of expression: [].count(');
});
it('should parse DoubleNegationBug', function (){
var scope = createScope();
assertEquals(true, scope.$eval('true'));
assertEquals(false, scope.$eval('!true'));
assertEquals(true, scope.$eval('!!true'));
assertEquals('a', scope.$eval('{true:"a", false:"b"}[!!true]'));
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 parse NegationBug', function () {
var scope = createScope();
assertEquals(!false || true, scope.$eval("!false || true"));
assertEquals(!11 == 10, scope.$eval("!11 == 10"));
assertEquals(12/6/2, scope.$eval("12/6/2"));
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 parse BugStringConfusesparser', function(){
var scope = createScope();
assertEquals('!', scope.$eval('suffix = "!"'));
it('should evaluate exclamation mark', function() {
expect(scope.$eval('suffix = "!"')).toEqual('!');
});
it('should parse ParsingBug', function () {
var scope = createScope();
assertEquals({a: "-"}, scope.$eval("{a:'-'}"));
it('should evaluate minus', function() {
expect(scope.$eval("{a:'-'}")).toEqual({a: "-"});
});
it('should parse Undefined', function () {
var scope = createScope();
assertEquals(undefined, scope.$eval("undefined"));
assertEquals(undefined, scope.$eval("a=undefined"));
assertEquals(undefined, scope.$get("a"));
it('should evaluate undefined', function() {
expect(scope.$eval("undefined")).not.toBeDefined();
expect(scope.$eval("a=undefined")).not.toBeDefined();
expect(scope.$get("a")).not.toBeDefined();
});
});
-6
View File
@@ -69,12 +69,6 @@ ValidatorTest.prototype.testPhone = function() {
assertEquals(null, angular.validator.phone("+421 0905 933 297"));
};
ValidatorTest.prototype.testSSN = function() {
var error = "SSN needs to be in 999-99-9999 format.";
assertEquals(angular.validator.ssn("ab"), error);
assertEquals(angular.validator.ssn("123-45-6789"), null);
};
ValidatorTest.prototype.testURL = function() {
var error = "URL needs to be in http://server[:port]/path format.";
assertEquals(angular.validator.url("ab"), error);
+33 -4
View File
@@ -195,12 +195,17 @@ angular.service('$browser', function(){
function TzDate(offset, timestamp) {
if (angular.isString(timestamp)) {
var tsStr = timestamp;
timestamp = angular.String.toDate(timestamp).getTime();
this.origDate = angular.String.toDate(timestamp);
timestamp = this.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
} else {
this.origDate = new Date(timestamp);
}
var localOffset = new Date(timestamp).getTimezoneOffset();
@@ -243,10 +248,34 @@ function TzDate(offset, timestamp) {
return offset * 60;
};
this.getUTCFullYear = function() {
return this.origDate.getUTCFullYear();
};
this.getUTCMonth = function() {
return this.origDate.getUTCMonth();
};
this.getUTCDate = function() {
return this.origDate.getUTCDate();
};
this.getUTCHours = function() {
return this.origDate.getUTCHours();
};
this.getUTCMinutes = function() {
return this.origDate.getUTCMinutes();
};
this.getUTCSeconds = function() {
return this.origDate.getUTCSeconds();
};
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDate', 'getUTCDay',
'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth',
'getUTCSeconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDay',
'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
+25 -3
View File
@@ -10,8 +10,8 @@ describe('TzDate', function() {
});
it('should take dateString as constructor argument', function() {
expect(new TzDate(0, '1970-01-01T00:00:00Z').getTime()).toBe(0);
expect(new TzDate(0, '2010-09-03T23:05:08Z').getTime()).toBe(1283555108000);
expect(new TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0);
expect(new TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023);
});
@@ -88,7 +88,7 @@ describe('TzDate', function() {
it('should create a date representing new year in Bratislava', function() {
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
expect(newYearInBratislava.getFullYear()).toBe(2010);
expect(newYearInBratislava.getMonth()).toBe(0);
@@ -96,4 +96,26 @@ describe('TzDate', function() {
expect(newYearInBratislava.getHours()).toBe(0);
expect(newYearInBratislava.getMinutes()).toBe(0);
});
it('should delegate all the UTC methods to the original UTC Date object', function() {
//from when created from string
var date1 = new 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 TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').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);
});
});
+66 -29
View File
@@ -135,49 +135,86 @@ describe("directives", function(){
expect(element.text()).toEqual('');
});
it('should ng:repeat over array', function(){
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
Array.prototype.extraProperty = "should be ignored";
scope.items = ['misko', 'shyam'];
scope.$eval();
expect(element.text()).toEqual('misko;shyam;');
delete Array.prototype.extraProperty;
describe('ng:repeat', function() {
scope.items = ['adam', 'kai', 'brad'];
scope.$eval();
expect(element.text()).toEqual('adam;kai;brad;');
it('should ng:repeat over array', function(){
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
scope.items = ['brad'];
scope.$eval();
expect(element.text()).toEqual('brad;');
Array.prototype.extraProperty = "should be ignored";
scope.items = ['misko', 'shyam'];
scope.$eval();
expect(element.text()).toEqual('misko;shyam;');
delete Array.prototype.extraProperty;
scope.items = ['adam', 'kai', 'brad'];
scope.$eval();
expect(element.text()).toEqual('adam;kai;brad;');
scope.items = ['brad'];
scope.$eval();
expect(element.text()).toEqual('brad;');
});
it('should ng:repeat over object', function(){
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
scope.$set('items', {misko:'swe', shyam:'set'});
scope.$eval();
expect(element.text()).toEqual('misko:swe;shyam:set;');
});
it('should error on wrong parsing of ng:repeat', function(){
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
var log = "";
log += element.attr('ng-exception') + ';';
log += element.hasClass('ng-exception') + ';';
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
});
it('should expose iterator offset as $index when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + $index + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
});
it('should expose iterator offset as $index when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
});
it('should expose iterator position as $position when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'doug', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
});
it('should expose iterator position as $position when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
});
});
it('should ng:repeat over object', function(){
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
scope.$set('items', {misko:'swe', shyam:'set'});
scope.$eval();
expect(element.text()).toEqual('misko:swe;shyam:set;');
});
it('should error on wrong parsing of ng:repeat', function(){
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
var log = "";
log += element.attr('ng-exception') + ';';
log += element.hasClass('ng-exception') + ';';
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
});
it('should ng:watch', function(){
var scope = compile('<div ng:watch="i: count = count + 1" ng:init="count = 0">');
scope.$eval();
scope.$eval();
expect(scope.$get('count')).toEqual(0);
expect(scope.$get('count')).toEqual(1);
scope.$set('i', 0);
scope.$eval();
scope.$eval();
expect(scope.$get('count')).toEqual(1);
expect(scope.$get('count')).toEqual(2);
});
describe('ng:click', function(){
+25 -3
View File
@@ -266,6 +266,26 @@ describe("angular.scenario.dsl", function() {
expect(doc.find('div').attr('class')).toEqual('bam');
});
it('should get css', function() {
doc.append('<div id="test" style="border: 1px solid red"></div>');
$root.dsl.element('#test').css('border');
expect($root.futureResult).toMatch(/red/);
});
it('should set css', function() {
doc.append('<div id="test" style="border: 1px solid red"></div>');
$root.dsl.element('#test').css('border', '1px solid green');
expect(doc.find('#test').css('border')).toMatch(/green/);
});
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('<input value="bar">');
$root.dsl.element('input').val();
@@ -325,8 +345,10 @@ describe("angular.scenario.dsl", function() {
beforeEach(function() {
doc.append(
'<ul>' +
' <li ng:repeat-index="0"><span ng:bind="name">misko</span><span ng:bind="gender">male</span></li>' +
' <li ng:repeat-index="1"><span ng:bind="name">felisa</span><span ng:bind="gender">female</span></li>' +
' <li ng:repeat-index="0"><span ng:bind="name" class="ng-binding">misko</span>' +
' <span ng:bind="test && gender" class="ng-binding">male</span></li>' +
' <li ng:repeat-index="1"><span ng:bind="name" class="ng-binding">felisa</span>' +
' <span ng:bind="gender | uppercase" class="ng-binding">female</span></li>' +
'</ul>'
);
chain = $root.dsl.repeater('ul li');
@@ -509,7 +531,7 @@ describe("angular.scenario.dsl", function() {
it('should change value in textarea', function() {
doc.append('<textarea name="test.textarea">something</textarea>');
var chain = $root.dsl.textarea('test.textarea');
var chain = $root.dsl.input('test.textarea');
chain.enter('foo');
expect(_jQuery('textarea[name="test.textarea"]').val()).toEqual('foo');
});
+26 -2
View File
@@ -430,7 +430,11 @@ describe("widget", function(){
describe('ng:switch', function(){
it('should switch on value change', function(){
compile('<ng:switch on="select"><div ng:switch-when="1">first:{{name}}</div><div ng:switch-when="2">second:{{name}}</div></ng:switch>');
compile('<ng:switch on="select">' +
'<div ng:switch-when="1">first:{{name}}</div>' +
'<div ng:switch-when="2">second:{{name}}</div>' +
'<div ng:switch-when="true">true:{{name}}</div>' +
'</ng:switch>');
expect(element.html()).toEqual('');
scope.select = 1;
scope.$eval();
@@ -444,8 +448,28 @@ describe("widget", function(){
scope.name = 'misko';
scope.$eval();
expect(element.text()).toEqual('second:misko');
scope.select = true;
scope.$eval();
expect(element.text()).toEqual('true:misko');
});
it("should compare stringified versions", function(){
var switchWidget = angular.widget('ng:switch');
expect(switchWidget.equals(true, 'true')).toEqual(true);
});
it('should switch on switch-when-default', function(){
compile('<ng:switch on="select">' +
'<div ng:switch-when="1">one</div>' +
'<div ng:switch-default>other</div>' +
'</ng:switch>');
scope.$eval();
expect(element.text()).toEqual('other');
scope.select = 1;
scope.$eval();
expect(element.text()).toEqual('one');
});
it("should match urls", function(){
var scope = angular.compile('<ng:switch on="url" using="route:params"><div ng:switch-when="/Book/:name">{{params.name}}</div></ng:switch>');
scope.url = '/Book/Moby';
@@ -459,7 +483,7 @@ describe("widget", function(){
expect(match).toBeFalsy();
});
it('should call init on switch', function(){
it('should call change on switch', function(){
var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng:switch-when="a">{{name}}</div></ng:switch>');
var cleared = false;
scope.url = 'a';
+2 -2
View File
@@ -1,4 +1,4 @@
# <angular/> build config file
---
version: 0.9.2
codename: faunal-mimicry
version: 0.9.3
codename: cold-resistance
+1 -1
View File
@@ -3,6 +3,6 @@
# run: watch watchr.rb
# note: make sure that you have jstd server running (server.sh) and a browser captured
watch( '^src/' ) do
watch( '^src/|^docs/' ) do
%x{ echo "\n\ndoc run started @ `date`" > logs/docs.log; node docs/collect.js &> logs/docs.log & }
end