Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0462422089 | |||
| bb460d6355 | |||
| bbb19a4d16 | |||
| ee5e881b9e | |||
| 690dfe000b | |||
| 91c835dc0e | |||
| dfa8baf59a | |||
| a8efd43d25 | |||
| 96abeb1074 | |||
| 5159eb7635 | |||
| 1ba8c2a33a | |||
| 659af29adb | |||
| 1fe7e3a130 | |||
| aec3c8478c | |||
| 5c887ddb66 | |||
| 0bd4a473a7 | |||
| dcf76e6816 | |||
| 56a3d52f45 | |||
| faa7d81b67 | |||
| 6bb2cd6ee2 | |||
| 2d61040fb0 | |||
| 99f25050a3 | |||
| ba5f8ee27f | |||
| 9a532002cf | |||
| 5524d2b0fb | |||
| d4839bac32 | |||
| 602369c6a2 | |||
| 34909520ae | |||
| 92e31b556f | |||
| 62c0e5c460 | |||
| c67af8a038 | |||
| 2da1de5a6d | |||
| ee8465bf10 | |||
| 9c6794a044 | |||
| 0ffb47bdb6 | |||
| ec885489a2 | |||
| 01c52e92b1 | |||
| 943377a091 | |||
| 72b7a1c531 | |||
| 9c0225512c | |||
| 40d7e66f40 | |||
| 1d52349440 | |||
| 3eb0c8bc67 | |||
| 42855e4363 | |||
| 4c61fc01f9 | |||
| 4fdab37659 | |||
| 841013a4c4 | |||
| 4e9a2aa10e | |||
| 4fc2141458 | |||
| 5b40e87ac6 | |||
| 1391f19fb4 | |||
| 04a4d8b061 | |||
| bbd87c9425 | |||
| 64063b5d41 | |||
| 833e0ae343 | |||
| d74ef497de | |||
| 6ddcf91861 | |||
| 8a867cee22 | |||
| 68217d427c | |||
| 1efef67b5f | |||
| a6cfa43c19 | |||
| b41bc98c54 | |||
| aaabeb8c5e | |||
| 05d4971abb | |||
| c6107fe8ac | |||
| 68f074c299 | |||
| c53a37ed91 |
@@ -0,0 +1,11 @@
|
||||
<?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:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> </resources>}"/>
|
||||
<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:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs/callback.js" type="1"/> <item path="/angular.js/docs/collect.js" type="1"/> <item path="/angular.js/docs/filter.template" type="1"/> </resources>}"/>
|
||||
<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"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
|
||||
</launchConfiguration>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
|
||||
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> </resources>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LAUNCH_CONFIGURATION_BUILD_SCOPE" value="${none}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
|
||||
</launchConfiguration>
|
||||
@@ -1,3 +1,5 @@
|
||||
build/
|
||||
angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
regression/temp.html
|
||||
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
workspace.xml
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Settings><!--This file was automatically generated by Ruby plugin.
|
||||
You are allowed to:
|
||||
1. Remove rake task
|
||||
2. Add existing rake tasks
|
||||
To add existing rake tasks automatically delete this file and reload the project.
|
||||
--><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Compile JavaScript" fullCmd="compile" taksId="compile" /><RakeTask description="Generate Externs" fullCmd="compileexterns" taksId="compileexterns" /><RakeTask description="Lint" fullCmd="lint" taksId="lint" /><RakeGroup description="" fullCmd="" taksId="server"><RakeTask description="Run JsTestDriver Server" fullCmd="server:start" taksId="start" /><RakeTask description="Run JavaScript tests against the server" fullCmd="server:test" taksId="test" /></RakeGroup><RakeTask description="Run JavaScript tests" fullCmd="test" taksId="test" /><RakeTask description="" fullCmd="default" taksId="default" /></RakeGroup></Settings>
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUBY_MODULE" version="4">
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
Generated
+1
-4
@@ -3,10 +3,7 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="ProjectDetails">
|
||||
<option name="projectName" value="master" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Ruby SDK 1.8.7 (/usr/bin/ruby)" project-jdk-type="RUBY_SDK" />
|
||||
<component name="ProjectRootManager" version="2" />
|
||||
<component name="SvnBranchConfigurationManager">
|
||||
<option name="mySupportsUserInfoFilter" value="true" />
|
||||
</component>
|
||||
|
||||
Generated
+1
-1
@@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/master.iml" filepath="$PROJECT_DIR$/.idea/master.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value>
|
||||
<option name="USE_SAME_INDENTS" value="true" />
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="js">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="jsp">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="sass">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="xml">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="yml">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
</value>
|
||||
</option>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash">
|
||||
<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>
|
||||
</component>
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="/usr/bin/rake" />
|
||||
<option name="PARAMETERS" value="compile" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+1
-2
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
|
||||
Generated
+500
@@ -0,0 +1,500 @@
|
||||
<?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>
|
||||
|
||||
@@ -20,6 +20,16 @@
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
||||
<triggers>auto,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?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="test/" kind="src" path="test"/>
|
||||
<classpathentry kind="src" path="test/test"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
|
||||
<classpathentry kind="output" path=""/>
|
||||
</classpath>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
- created documentation framework based on jsdoc syntax (commit 659af29a)
|
||||
- jsdoc parser
|
||||
- template generator
|
||||
- json generator
|
||||
- angular doc viewer app
|
||||
- scenario runner for all example code
|
||||
- documentation for all angular filters (commits 1fe7e3a1 & 1ba8c2a33)
|
||||
- docs
|
||||
- example code
|
||||
- scenario tests for example code
|
||||
|
||||
### Testability
|
||||
#### Scenario Runner
|
||||
- binding DSL in Scenario can now match bindings without specifying filters
|
||||
- dsl statements now accept a label argument to make test output more readable (issue #94)
|
||||
- dsl element() statement now implements most of the jQuery API (issue #106)
|
||||
- new browser() dsl statement for getting info about the emulated browser running the app
|
||||
(issue #109)
|
||||
- scenario runner is now compatible with IE8 (issue #93)
|
||||
- scenarior runner checks if URL would return a non-success status code (issue #100)
|
||||
- binding() DSL now accepts regular expressions
|
||||
- new textarea() scenario runner DSL for entering text into textareas
|
||||
|
||||
### Misc
|
||||
- lots of small bugfixes
|
||||
|
||||
### Breaking changes
|
||||
#### Scenario Runner
|
||||
- navigating to about:blank is no longer supported. It results in a sandbox error
|
||||
- navigateTo() is now browser().navigateTo(). Old code must be updated
|
||||
- file:// URLs are no longer supported for running a scenario. You must use a web server that
|
||||
implements HEAD
|
||||
|
||||
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
- added html sanitizer to fix the last few known security issues (issues #33 and #34)
|
||||
|
||||
### API
|
||||
- new ng:submit directive for creating onSubmit handlers on forms (issue #76)
|
||||
- the date filter now accepts milliseconds as well as date strings (issue #78)
|
||||
- the html filter now supports 'unsafe' option to bypass html sanitization
|
||||
|
||||
### Testability
|
||||
- lots of improvements related to the scenario runner (commit 40d7e66f)
|
||||
|
||||
### Demo
|
||||
- added a new demo application: Personal Log (src example/personalLog)
|
||||
|
||||
### Chores
|
||||
- lots of fixes to get all tests pass on IE
|
||||
- added TzDate type to allow us to create timezone idependent tests (issue #88)
|
||||
|
||||
### Breaking changes
|
||||
- $cookieStore service is not globally published any more, if you use it, you must request it via
|
||||
$inject as any other non-global service
|
||||
- html filter now sanitizes html content for XSS attacks which may result in different behavior
|
||||
|
||||
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
- angular.fromJson not safer (issue #57)
|
||||
- readString consumes invalid escapes (issue #56)
|
||||
- use new Function instead of eval (issue #52)
|
||||
|
||||
### Speed
|
||||
- css cleanup + inline all css and images in the main js (issue #64)
|
||||
|
||||
### Testability
|
||||
- initial version of the built-in end-to-end scenario runner (issues #50, #67, #70)
|
||||
|
||||
### API
|
||||
- allow ng:controller nesting (issue #39)
|
||||
- new built-in date format filter (issue #45)
|
||||
- $location needs method you call on updates (issue #32)
|
||||
|
||||
|
||||
### Chores
|
||||
- release versioning + file renaming (issue #69)
|
||||
|
||||
### Breaking changes
|
||||
- $location.parse was replaced with $location.update
|
||||
- all css and img files were inlined into the main js file, to support IE7 and older app must host
|
||||
angular-ie-compat.js file
|
||||
|
||||
### Big Thanks to Our Community Contributors
|
||||
- Vojta Jina
|
||||
@@ -6,9 +6,10 @@ ANGULAR = [
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/Parser.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
@@ -26,12 +27,16 @@ ANGULAR_SCENARIO = [
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/HtmlUI.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js',
|
||||
]
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
@@ -39,6 +44,12 @@ BUILD_DIR = 'build'
|
||||
task :default => [:compile, :test]
|
||||
|
||||
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
end
|
||||
|
||||
|
||||
desc 'Clean Generated Files'
|
||||
task :clean do
|
||||
FileUtils.rm_r(BUILD_DIR, :force => true)
|
||||
@@ -47,7 +58,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile Scenario'
|
||||
task :compile_scenario do
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
@@ -67,11 +78,8 @@ task :compile_scenario do
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
desc 'Generate IE css js patch'
|
||||
task :generate_ie_compat do
|
||||
task :generate_ie_compat => :init do
|
||||
css = File.open('css/angular.css', 'r') {|f| f.read }
|
||||
|
||||
# finds all css rules that contain backround images and extracts the rule name(s), content type of
|
||||
@@ -127,7 +135,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:compile_scenario, :generate_ie_compat] do
|
||||
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
@@ -148,8 +156,14 @@ task :compile => [:compile_scenario, :generate_ie_compat] do
|
||||
end
|
||||
|
||||
|
||||
desc 'Generate docs'
|
||||
task :docs do
|
||||
`node docs/collect.js`
|
||||
end
|
||||
|
||||
|
||||
desc 'Create angular distribution'
|
||||
task :package => :compile do
|
||||
task :package => [:clean, :compile, :docs] do
|
||||
v = YAML::load( File.open( 'version.yaml' ) )['version']
|
||||
match = v.match(/^([^-]*)(-snapshot)?$/)
|
||||
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
|
||||
@@ -170,6 +184,20 @@ task :package => :compile do
|
||||
FileUtils.cp(src, pkg_dir + '/' + dest)
|
||||
end
|
||||
|
||||
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.rewind
|
||||
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js")
|
||||
end
|
||||
|
||||
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
|
||||
|
||||
puts "Package created: #{path_to(tarball)}"
|
||||
@@ -248,5 +276,5 @@ end
|
||||
# returns path to the file in the build directory
|
||||
#
|
||||
def path_to(filename)
|
||||
return File.join(BUILD_DIR, filename)
|
||||
return File.join(BUILD_DIR, *filename)
|
||||
end
|
||||
|
||||
@@ -8,6 +8,15 @@ body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#system-error {
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#json, #xml {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@@ -32,7 +41,7 @@ body {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#frame h2,
|
||||
#application h2,
|
||||
#specs h2 {
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
@@ -45,26 +54,26 @@ body {
|
||||
}
|
||||
|
||||
#header,
|
||||
#frame,
|
||||
#application,
|
||||
.test-info,
|
||||
.test-actions li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
width: 100%;
|
||||
height: 758px;
|
||||
}
|
||||
|
||||
#frame .popout {
|
||||
#application .popout {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -154,6 +163,10 @@ body {
|
||||
margin-left: 6em;
|
||||
}
|
||||
|
||||
.test-describe {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.test-describe .test-describe {
|
||||
margin: 5px 5px 10px 2em;
|
||||
}
|
||||
@@ -178,11 +191,11 @@ body {
|
||||
}
|
||||
|
||||
#specs h2,
|
||||
#frame h2 {
|
||||
#application h2 {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
border: 1px solid #BABAD1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
function noop(){}
|
||||
|
||||
function chain(delegateFn, explicitDone){
|
||||
var onDoneFn = noop;
|
||||
var onErrorFn = noop;
|
||||
var waitForCount = 1;
|
||||
delegateFn = delegateFn || noop;
|
||||
var stackError = new Error('capture stack');
|
||||
|
||||
function decrementWaitFor() {
|
||||
waitForCount--;
|
||||
if (waitForCount == 0)
|
||||
onDoneFn();
|
||||
}
|
||||
|
||||
function self(){
|
||||
try {
|
||||
return delegateFn.apply(self, arguments);
|
||||
} catch (error) {
|
||||
self.error(error);
|
||||
} finally {
|
||||
if (!explicitDone)
|
||||
decrementWaitFor();
|
||||
}
|
||||
};
|
||||
self.onDone = function(callback){
|
||||
onDoneFn = callback;
|
||||
return self;
|
||||
};
|
||||
self.onError = function(callback){
|
||||
onErrorFn = callback;
|
||||
return self;
|
||||
};
|
||||
self.waitFor = function(callback){
|
||||
if (waitForCount == 0)
|
||||
throw new Error("Can not wait on already called callback.");
|
||||
waitForCount++;
|
||||
return chain(callback).onDone(decrementWaitFor).onError(self.error);
|
||||
};
|
||||
|
||||
self.waitMany = function(callback){
|
||||
if (waitForCount == 0)
|
||||
throw new Error("Can not wait on already called callback.");
|
||||
waitForCount++;
|
||||
return chain(callback, true).onDone(decrementWaitFor).onError(self.error);
|
||||
};
|
||||
|
||||
self.done = function(callback){
|
||||
decrementWaitFor();
|
||||
};
|
||||
|
||||
self.error = function(error) {
|
||||
var stack = stackError.stack.split(/\n\r?/).splice(2);
|
||||
var nakedStack = [];
|
||||
stack.forEach(function(frame){
|
||||
if (!frame.match(/callback\.js:\d+:\d+\)$/))
|
||||
nakedStack.push(frame);
|
||||
});
|
||||
error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n');
|
||||
onErrorFn(error);
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
exports.chain = chain;
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
var fs = require('fs'),
|
||||
spawn = require('child_process').spawn,
|
||||
mustache = require('../lib/mustache'),
|
||||
callback = require('./callback'),
|
||||
markdown = require('../lib/markdown');
|
||||
|
||||
var documentation = {
|
||||
section:{},
|
||||
all:[]
|
||||
};
|
||||
|
||||
var SRC_DIR = "docs/";
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
|
||||
var work = callback.chain(function () {
|
||||
console.log('Parsing Angular Reference Documentation');
|
||||
mkdirPath(OUTPUT_DIR, work.waitFor(function(){
|
||||
findJsFiles('src', work.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDoc(file, work.waitMany(function(doc) {
|
||||
parseNgDoc(doc);
|
||||
if (doc.ngdoc) {
|
||||
delete doc.raw.text;
|
||||
var section = documentation.section;
|
||||
(section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc);
|
||||
documentation.all.push(doc);
|
||||
console.log('Found:', doc.ngdoc + ':' + doc.shortName);
|
||||
mergeTemplate(
|
||||
doc.ngdoc + '.template',
|
||||
doc.name + '.html', doc, work.waitFor());
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}).onError(function(err){
|
||||
console.log('ERROR:', err.stack || err);
|
||||
}).onDone(function(){
|
||||
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain());
|
||||
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
|
||||
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());
|
||||
console.log('DONE');
|
||||
});
|
||||
work();
|
||||
////////////////////
|
||||
|
||||
function noop(){}
|
||||
function mkdirPath(path, callback) {
|
||||
var parts = path.split(/\//);
|
||||
path = '.';
|
||||
(function next(){
|
||||
if (parts.length) {
|
||||
path += '/' + parts.shift();
|
||||
fs.mkdir(path, 0777, next);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function copy(name, callback){
|
||||
fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
fs.writeFile(OUTPUT_DIR + name, content, callback);
|
||||
}));
|
||||
}
|
||||
|
||||
function mergeTemplate(template, output, doc, callback){
|
||||
fs.readFile(SRC_DIR + template,
|
||||
callback.waitFor(function(err, template){
|
||||
if (err) return this.error(err);
|
||||
var content = mustache.to_html(template.toString(), doc);
|
||||
fs.writeFile(OUTPUT_DIR + output, content, callback);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function unknownTag(doc, name) {
|
||||
var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name;
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
function valueTag(doc, name, value) {
|
||||
doc[name] = value;
|
||||
}
|
||||
|
||||
function escapedHtmlTag(doc, name, value) {
|
||||
doc[name] = value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function markdownTag(doc, name, value) {
|
||||
doc[name] = markdown.toHTML(value);
|
||||
}
|
||||
|
||||
var TAG = {
|
||||
ngdoc: valueTag,
|
||||
example: escapedHtmlTag,
|
||||
scenario: valueTag,
|
||||
namespace: valueTag,
|
||||
css: valueTag,
|
||||
see: valueTag,
|
||||
'function': valueTag,
|
||||
description: markdownTag,
|
||||
returns: markdownTag,
|
||||
name: function(doc, name, value) {
|
||||
doc.name = value;
|
||||
doc.shortName = value.split(/\./).pop();
|
||||
},
|
||||
param: function(doc, name, value){
|
||||
doc.param = doc.param || [];
|
||||
doc.paramRest = doc.paramRest || [];
|
||||
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]};
|
||||
doc.param.push(param);
|
||||
if (!doc.paramFirst) {
|
||||
doc.paramFirst = param;
|
||||
} else {
|
||||
doc.paramRest.push(param);
|
||||
}
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @param must be in format '{type} name=value description' got: " + value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function parseNgDoc(doc){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
doc.raw.text.split(/\n/).forEach(function(line, lineNumber){
|
||||
if (match = line.match(/^@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
// if we have existing name
|
||||
if (atName) {
|
||||
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
|
||||
}
|
||||
atName = match[1];
|
||||
atText = [];
|
||||
if(match[3]) atText.push(match[3]);
|
||||
} else {
|
||||
if (atName) {
|
||||
atText.push(line);
|
||||
} else {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
if (atName) {
|
||||
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
function findNgDoc(file, callback) {
|
||||
fs.readFile(file, callback.waitFor(function(err, content){
|
||||
var lines = content.toString().split(/\n\r?/);
|
||||
var doc;
|
||||
var match;
|
||||
var inDoc = false;
|
||||
lines.forEach(function(line, lineNumber){
|
||||
lineNumber++;
|
||||
// is the comment starting?
|
||||
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
|
||||
line = match[1];
|
||||
inDoc = true;
|
||||
doc = {raw:{file:file, line:lineNumber, text:[]}};
|
||||
}
|
||||
// are we done?
|
||||
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/))
|
||||
callback(doc);
|
||||
doc = null;
|
||||
inDoc = false;
|
||||
}
|
||||
// is the comment add text
|
||||
if (inDoc){
|
||||
doc.raw.text.push(line.replace(/^\s*\*\s?/, ''));
|
||||
}
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function findJsFiles(dir, callback){
|
||||
fs.readdir(dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var path = dir + '/' + file;
|
||||
fs.lstat(path, callback.waitFor(function(err, stat){
|
||||
if (err) return this.error(err);
|
||||
if (stat.isDirectory())
|
||||
findJsFiles(path, callback.waitMany(callback));
|
||||
else if (/\.js$/.test(path))
|
||||
callback(path);
|
||||
}));
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
NG_DOC={{{JSON}}};
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title><angular/> Docs Scenario Runner</title>
|
||||
<script type="text/javascript" src="../angular-scenario.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="docs-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
{{#all}}
|
||||
describe('{{name}}', function(){
|
||||
beforeEach(function(){
|
||||
browser().navigateTo('index.html#{{name}}');
|
||||
});
|
||||
// {{raw.file}}:{{raw.line}}
|
||||
{{{scenario}}}
|
||||
});
|
||||
{{/all}}
|
||||
@@ -0,0 +1,7 @@
|
||||
function DocController($resource, $location){
|
||||
this.docs = $resource('documentation.json').get();
|
||||
this.getPartialDoc = function(){
|
||||
return encodeURIComponent($location.hashPath) + '.html';
|
||||
};
|
||||
}
|
||||
DocController.$inject=['$resource', '$location'];
|
||||
@@ -0,0 +1,33 @@
|
||||
<h1><tt>{{name}}</tt></h1>
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<span>{{</span>
|
||||
{{paramFirst.name}}_expression
|
||||
| {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}={{default}}]</i>{{/default}}{{/paramRest}}
|
||||
<span> }}</span>
|
||||
</tt>
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}{{#type}}({{type}}){{/type}}</tt>: {{description}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
|
||||
<h3>Returns</h3>
|
||||
{{{returns}}}
|
||||
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<WIKI:SOURCE style="display:block;">
|
||||
{{{example}}}
|
||||
</WIKI:SOURCE>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
|
||||
<head>
|
||||
<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" />
|
||||
</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>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
{{{description}}}
|
||||
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
}
|
||||
//////////////////
|
||||
@@ -0,0 +1,51 @@
|
||||
(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"><angular/></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');
|
||||
});
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -23,7 +23,7 @@
|
||||
<ul>
|
||||
<li ng:repeat="item in activities.data.items.$filter(filterText)">
|
||||
<h1>
|
||||
<img src="{{item.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{item.actor.thumbnailUrl}}"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href="#" ng:click="expandReplies(item)">Replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
@@ -31,14 +31,14 @@
|
||||
{{item.object.content | html}}
|
||||
<div>
|
||||
<a href="{{attachment.links.enclosure[0].href}}" ng:repeat="attachment in item.object.attachments">
|
||||
<img src="{{attachment.links.preview[0].href}}"/>
|
||||
<img ng:src="{{attachment.links.preview[0].href}}"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<my:expand expand="item.replies.show">
|
||||
<ul>
|
||||
<li ng:repeat="reply in item.replies.data.items">
|
||||
<img src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title>Personal Log</title>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="personalLog.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
|
||||
must be a better way to do this -->
|
||||
<body ng:controller="example.personalLog.LogCtrl" ng:init="$window.$root = $root">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" name="newMsg" />
|
||||
<input type="submit" value="add" />
|
||||
<input type="button" value="remove all" ng:click="rmLogs()" />
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Logs:</h2>
|
||||
<ul>
|
||||
<li ng:repeat="log in logs.$orderBy('-at')">
|
||||
{{log.at | date:'yy-MM-dd HH:mm'}} {{log.msg}}
|
||||
[<a href="" ng:click="rmLog(log)">x</a>]
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @fileOverview Very simple personal log demo application to demonstrate angular functionality,
|
||||
* especially:
|
||||
* - the MVC model
|
||||
* - testability of controllers
|
||||
* - dependency injection for controllers via $inject and constructor function
|
||||
* - $cookieStore for persistent cookie-backed storage
|
||||
* - simple templating constructs such as ng:repeat and {{}}
|
||||
* - date filter
|
||||
* - and binding onSubmit and onClick events to angular expressions
|
||||
* @author Igor Minar
|
||||
*/
|
||||
|
||||
|
||||
/** @namespace the 'example' namespace */
|
||||
var example = example || {};
|
||||
/** @namespace namespace of the personal log app */
|
||||
example.personalLog = {};
|
||||
|
||||
|
||||
//name space isolating closure
|
||||
(function() {
|
||||
|
||||
var LOGS = 'logs';
|
||||
|
||||
/**
|
||||
* The controller for the personal log app.
|
||||
*/
|
||||
function LogCtrl($cookieStore) {
|
||||
var self = this,
|
||||
logs = self.logs = $cookieStore.get(LOGS) || []; //main model
|
||||
|
||||
|
||||
/**
|
||||
* Adds newMsg to the logs array as a log, persists it and clears newMsg.
|
||||
* @param {string} msg Message to add (message is passed as parameter to make testing easier).
|
||||
*/
|
||||
this.addLog = function(msg) {
|
||||
var newMsg = msg || self.newMsg;
|
||||
if (!newMsg) return;
|
||||
var log = {
|
||||
at: new Date().getTime(),
|
||||
msg: newMsg
|
||||
}
|
||||
|
||||
logs.push(log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
self.newMsg = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes a log from logs.
|
||||
* @param {object} log The log to remove.
|
||||
*/
|
||||
this.rmLog = function(log) {
|
||||
angular.Array.remove(logs, log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes all logs.
|
||||
*/
|
||||
this.rmLogs = function() {
|
||||
logs.splice(0, logs.length);
|
||||
$cookieStore.remove(LOGS);
|
||||
};
|
||||
}
|
||||
|
||||
//inject
|
||||
LogCtrl.$inject = ['$cookieStore'];
|
||||
|
||||
//export
|
||||
example.personalLog.LogCtrl = LogCtrl;
|
||||
})();
|
||||
@@ -0,0 +1,97 @@
|
||||
describe('personal log', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../personalLog.html');
|
||||
});
|
||||
|
||||
|
||||
afterEach(function() {
|
||||
clearCookies();
|
||||
});
|
||||
|
||||
|
||||
it('should create new logs and order them in reverse chronological order', function(){
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my first message');
|
||||
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual(['my second message', 'my first message']);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a log when user clicks on the related X link', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('ul li a:eq(1)').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my second message');
|
||||
|
||||
element('ul li a:eq(0)').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should delete all cookies when user clicks on "remove all" button', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('input[value="remove all"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should preserve logs over page reloads', function() {
|
||||
input('newMsg').enter('my persistent message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
|
||||
browser().reload();
|
||||
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my persistent message');
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DSL for deleting all cookies.
|
||||
*/
|
||||
angular.scenario.dsl('clearCookies', function() {
|
||||
/**
|
||||
* Deletes cookies by interacting with the cookie service within the application under test.
|
||||
*/
|
||||
return function() {
|
||||
this.addFutureAction('clear all cookies', function($window, $document, done) {
|
||||
//TODO: accessing angular services is pretty nasty, we need a better way to reach them
|
||||
var $cookies = $window.$root.$cookies,
|
||||
cookieName;
|
||||
|
||||
for (cookieName in $cookies) {
|
||||
console.log('deleting cookie: ' + cookieName);
|
||||
delete $cookies[cookieName];
|
||||
}
|
||||
$window.$root.$eval();
|
||||
|
||||
done();
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>Personal Log Scenario Runner</title>
|
||||
<script type="text/javascript" src="../../../src/scenario/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="personalLogScenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,122 @@
|
||||
describe('example.personalLog.LogCtrl', function() {
|
||||
var logCtrl;
|
||||
|
||||
function createNotesCtrl() {
|
||||
var scope = angular.scope();
|
||||
return scope.$new(example.personalLog.LogCtrl);
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl = createNotesCtrl();
|
||||
});
|
||||
|
||||
|
||||
it('should initialize notes with an empty array', function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
describe('addLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should add newMsg to logs as a log entry', function() {
|
||||
logCtrl.newMsg = 'first log message';
|
||||
logCtrl.addLog();
|
||||
|
||||
expect(logCtrl.logs.length).toBe(1);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
|
||||
//one more msg, this time passed in as param
|
||||
logCtrl.addLog('second log message');
|
||||
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
expect(logCtrl.logs[1].msg).toBe('second log message');
|
||||
});
|
||||
|
||||
|
||||
it('should clear newMsg when log entry is persisted', function() {
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.newMsg).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should store logs in the logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should do nothing if newMsg is empty', function() {
|
||||
logCtrl.addLog('');
|
||||
expect(logCtrl.logs.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a message identified by index', function() {
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.logs.length).toBe(3);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[2]);
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('message1');
|
||||
expect(logCtrl.logs[1].msg).toBe('message3');
|
||||
});
|
||||
|
||||
|
||||
it('should update cookies when a log is deleted', function() {
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\]/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLogs', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should remove all logs', function() {
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should remove logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/local/bin/node docs/collect.js
|
||||
@@ -1,7 +1,7 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine/jasmine-1.0.1.js
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_alias.js
|
||||
@@ -10,9 +10,11 @@ load:
|
||||
- src/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
|
||||
exclude:
|
||||
@@ -20,6 +22,6 @@ exclude:
|
||||
- src/angular.suffix
|
||||
- src/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
- src/scenario/bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- test/jquery_remove.js
|
||||
|
||||
|
||||
+6
-2
@@ -1,24 +1,28 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine/jasmine-1.0.1.js
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- test/jquery_alias.js
|
||||
- src/angular.prefix
|
||||
- src/angular.suffix
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
|
||||
endTag = /^<\/(\w+)[^>]*>/,
|
||||
attr = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var special = makeMap("script,style");
|
||||
|
||||
var htmlParser = this.htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){
|
||||
return this[ this.length - 1 ];
|
||||
};
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !special[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") == 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( html.indexOf("</") == 0 ) {
|
||||
match = html.match( endTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( endTag, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( html.indexOf("<") == 0 ) {
|
||||
match = html.match( startTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( startTag, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
|
||||
text = text.replace(/<!--(.*?)-->/g, "$1")
|
||||
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last )
|
||||
throw "Parse Error: " + html;
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
if ( block[ tagName ] ) {
|
||||
while ( stack.last() && inline[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelf[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = empty[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = [];
|
||||
|
||||
rest.replace(attr, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
// If no tag name is provided, clean shop
|
||||
if ( !tagName )
|
||||
var pos = 0;
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
else
|
||||
for ( var pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( var i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.HTMLtoXML = function( html ) {
|
||||
var results = "";
|
||||
|
||||
htmlParser(html, {
|
||||
start: function( tag, attrs, unary ) {
|
||||
results += "<" + tag;
|
||||
|
||||
for ( var i = 0; i < attrs.length; i++ )
|
||||
results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
|
||||
|
||||
results += (unary ? "/" : "") + ">";
|
||||
},
|
||||
end: function( tag ) {
|
||||
results += "</" + tag + ">";
|
||||
},
|
||||
chars: function( text ) {
|
||||
results += text;
|
||||
},
|
||||
comment: function( text ) {
|
||||
results += "<!--" + text + "-->";
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
this.HTMLtoDOM = function( html, doc ) {
|
||||
// There can be only one of these elements
|
||||
var one = makeMap("html,head,body,title");
|
||||
|
||||
// Enforce a structure for the document
|
||||
var structure = {
|
||||
link: "head",
|
||||
base: "head"
|
||||
};
|
||||
|
||||
if ( !doc ) {
|
||||
if ( typeof DOMDocument != "undefined" )
|
||||
doc = new DOMDocument();
|
||||
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
|
||||
doc = document.implementation.createDocument("", "", null);
|
||||
else if ( typeof ActiveX != "undefined" )
|
||||
doc = new ActiveXObject("Msxml.DOMDocument");
|
||||
|
||||
} else
|
||||
doc = doc.ownerDocument ||
|
||||
doc.getOwnerDocument && doc.getOwnerDocument() ||
|
||||
doc;
|
||||
|
||||
var elems = [],
|
||||
documentElement = doc.documentElement ||
|
||||
doc.getDocumentElement && doc.getDocumentElement();
|
||||
|
||||
// If we're dealing with an empty document then we
|
||||
// need to pre-populate it with the HTML document structure
|
||||
if ( !documentElement && doc.createElement ) (function(){
|
||||
var html = doc.createElement("html");
|
||||
var head = doc.createElement("head");
|
||||
head.appendChild( doc.createElement("title") );
|
||||
html.appendChild( head );
|
||||
html.appendChild( doc.createElement("body") );
|
||||
doc.appendChild( html );
|
||||
})();
|
||||
|
||||
// Find all the unique elements
|
||||
if ( doc.getElementsByTagName )
|
||||
for ( var i in one )
|
||||
one[ i ] = doc.getElementsByTagName( i )[0];
|
||||
|
||||
// If we're working with a document, inject contents into
|
||||
// the body element
|
||||
var curParentNode = one.body;
|
||||
|
||||
htmlParser( html, {
|
||||
start: function( tagName, attrs, unary ) {
|
||||
// If it's a pre-built element, then we can ignore
|
||||
// its construction
|
||||
if ( one[ tagName ] ) {
|
||||
curParentNode = one[ tagName ];
|
||||
return;
|
||||
}
|
||||
|
||||
var elem = doc.createElement( tagName );
|
||||
|
||||
for ( var attr in attrs )
|
||||
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
|
||||
|
||||
if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
|
||||
one[ structure[ tagName ] ].appendChild( elem );
|
||||
|
||||
else if ( curParentNode && curParentNode.appendChild )
|
||||
curParentNode.appendChild( elem );
|
||||
|
||||
if ( !unary ) {
|
||||
elems.push( elem );
|
||||
curParentNode = elem;
|
||||
}
|
||||
},
|
||||
end: function( tag ) {
|
||||
elems.length -= 1;
|
||||
|
||||
// Init the new parentNode
|
||||
curParentNode = elems[ elems.length - 1 ];
|
||||
},
|
||||
chars: function( text ) {
|
||||
curParentNode.appendChild( doc.createTextNode( text ) );
|
||||
},
|
||||
comment: function( text ) {
|
||||
// create comment node
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(",");
|
||||
for ( var i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-2010 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,188 @@
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount == 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap["spec"]) return true;
|
||||
return spec.getFullName().indexOf(paramMap["spec"]) == 0;
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
body {
|
||||
font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
|
||||
}
|
||||
|
||||
|
||||
.jasmine_reporter a:visited, .jasmine_reporter a {
|
||||
color: #303;
|
||||
}
|
||||
|
||||
.jasmine_reporter a:hover, .jasmine_reporter a:active {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.run_spec {
|
||||
float:right;
|
||||
padding-right: 5px;
|
||||
font-size: .8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jasmine_reporter {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
color: #303;
|
||||
background-color: #fef;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: left;
|
||||
font-size: 1.1em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.logo .version {
|
||||
font-size: .6em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.runner.running {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
|
||||
.options {
|
||||
text-align: right;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.suite {
|
||||
border: 1px outset gray;
|
||||
margin: 5px 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.suite .suite {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.suite.passed {
|
||||
background-color: #dfd;
|
||||
}
|
||||
|
||||
.suite.failed {
|
||||
background-color: #fdd;
|
||||
}
|
||||
|
||||
.spec {
|
||||
margin: 5px;
|
||||
padding-left: 1em;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.spec.failed, .spec.passed, .spec.skipped {
|
||||
padding-bottom: 5px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.spec.failed {
|
||||
background-color: #fbb;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.spec.passed {
|
||||
background-color: #bfb;
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.spec.skipped {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
.messages {
|
||||
border-left: 1px dashed gray;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.passed {
|
||||
background-color: #cfc;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.failed {
|
||||
background-color: #fbb;
|
||||
}
|
||||
|
||||
.skipped {
|
||||
color: #777;
|
||||
background-color: #eee;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*.resultMessage {*/
|
||||
/*white-space: pre;*/
|
||||
/*}*/
|
||||
|
||||
.resultMessage span.result {
|
||||
display: block;
|
||||
line-height: 2em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.resultMessage .mismatch {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.stackTrace {
|
||||
white-space: pre;
|
||||
font-size: .8em;
|
||||
margin-left: 10px;
|
||||
max-height: 5em;
|
||||
overflow: auto;
|
||||
border: 1px inset red;
|
||||
padding: 1em;
|
||||
background: #eef;
|
||||
}
|
||||
|
||||
.finished-at {
|
||||
padding-left: 1em;
|
||||
font-size: .6em;
|
||||
}
|
||||
|
||||
.show-passed .passed,
|
||||
.show-skipped .skipped {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#jasmine_content {
|
||||
position:fixed;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.runner {
|
||||
border: 1px solid gray;
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
padding: 2px 0 2px 10px;
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
Copyright (c) 2009 Chris Wanstrath (Ruby)
|
||||
Copyright (c) 2010 Jan Lehnardt (JavaScript)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* CommonJS-compatible mustache.js module
|
||||
*
|
||||
* See http://github.com/janl/mustache.js for more info.
|
||||
*/
|
||||
/*
|
||||
mustache.js Ñ Logic-less templates in JavaScript
|
||||
|
||||
See http://mustache.github.com/ for more info.
|
||||
*/
|
||||
|
||||
var Mustache = function() {
|
||||
var Renderer = function() {};
|
||||
|
||||
Renderer.prototype = {
|
||||
otag: "{{",
|
||||
ctag: "}}",
|
||||
pragmas: {},
|
||||
buffer: [],
|
||||
pragmas_implemented: {
|
||||
"IMPLICIT-ITERATOR": true
|
||||
},
|
||||
context: {},
|
||||
|
||||
render: function(template, context, partials, in_recursion) {
|
||||
// reset buffer & set context
|
||||
if(!in_recursion) {
|
||||
this.context = context;
|
||||
this.buffer = []; // TODO: make this non-lazy
|
||||
}
|
||||
|
||||
// fail fast
|
||||
if(!this.includes("", template)) {
|
||||
if(in_recursion) {
|
||||
return template;
|
||||
} else {
|
||||
this.send(template);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template = this.render_pragmas(template);
|
||||
var html = this.render_section(template, context, partials);
|
||||
if(in_recursion) {
|
||||
return this.render_tags(html, context, partials, in_recursion);
|
||||
}
|
||||
|
||||
this.render_tags(html, context, partials, in_recursion);
|
||||
},
|
||||
|
||||
/*
|
||||
Sends parsed lines
|
||||
*/
|
||||
send: function(line) {
|
||||
if(line != "") {
|
||||
this.buffer.push(line);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Looks for %PRAGMAS
|
||||
*/
|
||||
render_pragmas: function(template) {
|
||||
// no pragmas
|
||||
if(!this.includes("%", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
|
||||
this.ctag);
|
||||
return template.replace(regex, function(match, pragma, options) {
|
||||
if(!that.pragmas_implemented[pragma]) {
|
||||
throw({message:
|
||||
"This implementation of mustache doesn't understand the '" +
|
||||
pragma + "' pragma"});
|
||||
}
|
||||
that.pragmas[pragma] = {};
|
||||
if(options) {
|
||||
var opts = options.split("=");
|
||||
that.pragmas[pragma][opts[0]] = opts[1];
|
||||
}
|
||||
return "";
|
||||
// ignore unknown pragmas silently
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Tries to find a partial in the curent scope and render it
|
||||
*/
|
||||
render_partial: function(name, context, partials) {
|
||||
name = this.trim(name);
|
||||
if(!partials || partials[name] === undefined) {
|
||||
throw({message: "unknown_partial '" + name + "'"});
|
||||
}
|
||||
if(typeof(context[name]) != "object") {
|
||||
return this.render(partials[name], context, partials, true);
|
||||
}
|
||||
return this.render(partials[name], context[name], partials, true);
|
||||
},
|
||||
|
||||
/*
|
||||
Renders inverted (^) and normal (#) sections
|
||||
*/
|
||||
render_section: function(template, context, partials) {
|
||||
if(!this.includes("#", template) && !this.includes("^", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
// CSW - Added "+?" so it finds the tighest bound, not the widest
|
||||
var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
|
||||
"\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
|
||||
"\\s*", "mg");
|
||||
|
||||
// for each {{#foo}}{{/foo}} section do...
|
||||
return template.replace(regex, function(match, type, name, content) {
|
||||
var value = that.find(name, context);
|
||||
if(type == "^") { // inverted section
|
||||
if(!value || that.is_array(value) && value.length === 0) {
|
||||
// false or empty list, render it
|
||||
return that.render(content, context, partials, true);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else if(type == "#") { // normal section
|
||||
if(that.is_array(value)) { // Enumerable, Let's loop!
|
||||
return that.map(value, function(row) {
|
||||
return that.render(content, that.create_context(row),
|
||||
partials, true);
|
||||
}).join("");
|
||||
} else if(that.is_object(value)) { // Object, Use it as subcontext!
|
||||
return that.render(content, that.create_context(value),
|
||||
partials, true);
|
||||
} else if(typeof value === "function") {
|
||||
// higher order section
|
||||
return value.call(context, content, function(text) {
|
||||
return that.render(text, context, partials, true);
|
||||
});
|
||||
} else if(value) { // boolean section
|
||||
return that.render(content, context, partials, true);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Replace {{foo}} and friends with values from our view
|
||||
*/
|
||||
render_tags: function(template, context, partials, in_recursion) {
|
||||
// tit for tat
|
||||
var that = this;
|
||||
|
||||
var new_regex = function() {
|
||||
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
|
||||
that.ctag + "+", "g");
|
||||
};
|
||||
|
||||
var regex = new_regex();
|
||||
var tag_replace_callback = function(match, operator, name) {
|
||||
switch(operator) {
|
||||
case "!": // ignore comments
|
||||
return "";
|
||||
case "=": // set new delimiters, rebuild the replace regexp
|
||||
that.set_delimiters(name);
|
||||
regex = new_regex();
|
||||
return "";
|
||||
case ">": // render partial
|
||||
return that.render_partial(name, context, partials);
|
||||
case "{": // the triple mustache is unescaped
|
||||
return that.find(name, context);
|
||||
default: // escape the value
|
||||
return that.escape(that.find(name, context));
|
||||
}
|
||||
};
|
||||
var lines = template.split("\n");
|
||||
for(var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
|
||||
if(!in_recursion) {
|
||||
this.send(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(in_recursion) {
|
||||
return lines.join("\n");
|
||||
}
|
||||
},
|
||||
|
||||
set_delimiters: function(delimiters) {
|
||||
var dels = delimiters.split(" ");
|
||||
this.otag = this.escape_regex(dels[0]);
|
||||
this.ctag = this.escape_regex(dels[1]);
|
||||
},
|
||||
|
||||
escape_regex: function(text) {
|
||||
// thank you Simon Willison
|
||||
if(!arguments.callee.sRE) {
|
||||
var specials = [
|
||||
'/', '.', '*', '+', '?', '|',
|
||||
'(', ')', '[', ']', '{', '}', '\\'
|
||||
];
|
||||
arguments.callee.sRE = new RegExp(
|
||||
'(\\' + specials.join('|\\') + ')', 'g'
|
||||
);
|
||||
}
|
||||
return text.replace(arguments.callee.sRE, '\\$1');
|
||||
},
|
||||
|
||||
/*
|
||||
find `name` in current `context`. That is find me a value
|
||||
from the view object
|
||||
*/
|
||||
find: function(name, context) {
|
||||
name = this.trim(name);
|
||||
|
||||
// Checks whether a value is thruthy or false or 0
|
||||
function is_kinda_truthy(bool) {
|
||||
return bool === false || bool === 0 || bool;
|
||||
}
|
||||
|
||||
var value = context;
|
||||
var path = name.split(/\./);
|
||||
for(var i = 0; i < path.length; i++) {
|
||||
name = path[i];
|
||||
if(value && is_kinda_truthy(value[name])) {
|
||||
value = value[name];
|
||||
} else if(i == 0 && is_kinda_truthy(this.context[name])) {
|
||||
value = this.context[name];
|
||||
} else {
|
||||
value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof value === "function") {
|
||||
return value.apply(context);
|
||||
}
|
||||
if(value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
// silently ignore unkown variables
|
||||
return "";
|
||||
},
|
||||
|
||||
// Utility methods
|
||||
|
||||
/* includes tag */
|
||||
includes: function(needle, haystack) {
|
||||
return haystack.indexOf(this.otag + needle) != -1;
|
||||
},
|
||||
|
||||
/*
|
||||
Does away with nasty characters
|
||||
*/
|
||||
escape: function(s) {
|
||||
s = String(s === null ? "" : s);
|
||||
return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
|
||||
switch(s) {
|
||||
case "&": return "&";
|
||||
case "\\": return "\\\\";
|
||||
case '"': return '"';
|
||||
case "'": return ''';
|
||||
case "<": return "<";
|
||||
case ">": return ">";
|
||||
default: return s;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// by @langalex, support for arrays of strings
|
||||
create_context: function(_context) {
|
||||
if(this.is_object(_context)) {
|
||||
return _context;
|
||||
} else {
|
||||
var iterator = ".";
|
||||
if(this.pragmas["IMPLICIT-ITERATOR"]) {
|
||||
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
|
||||
}
|
||||
var ctx = {};
|
||||
ctx[iterator] = _context;
|
||||
return ctx;
|
||||
}
|
||||
},
|
||||
|
||||
is_object: function(a) {
|
||||
return a && typeof a == "object";
|
||||
},
|
||||
|
||||
is_array: function(a) {
|
||||
return Object.prototype.toString.call(a) === '[object Array]';
|
||||
},
|
||||
|
||||
/*
|
||||
Gets rid of leading and trailing whitespace
|
||||
*/
|
||||
trim: function(s) {
|
||||
return s.replace(/^\s*|\s*$/g, "");
|
||||
},
|
||||
|
||||
/*
|
||||
Why, why, why? Because IE. Cry, cry cry.
|
||||
*/
|
||||
map: function(array, fn) {
|
||||
if (typeof array.map == "function") {
|
||||
return array.map(fn);
|
||||
} else {
|
||||
var r = [];
|
||||
var l = array.length;
|
||||
for(var i = 0; i < l; i++) {
|
||||
r.push(fn(array[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return({
|
||||
name: "mustache.js",
|
||||
version: "0.3.1-dev",
|
||||
|
||||
/*
|
||||
Turns a template and view into HTML
|
||||
*/
|
||||
to_html: function(template, view, partials, send_fun) {
|
||||
var renderer = new Renderer();
|
||||
if(send_fun) {
|
||||
renderer.send = send_fun;
|
||||
}
|
||||
renderer.render(template, view, partials);
|
||||
if(!send_fun) {
|
||||
return renderer.buffer.join("\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
}();
|
||||
|
||||
|
||||
exports.name = Mustache.name;
|
||||
exports.version = Mustache.version;
|
||||
|
||||
exports.to_html = function() {
|
||||
return Mustache.to_html.apply(this, arguments);
|
||||
};
|
||||
+236
-17
@@ -1,22 +1,241 @@
|
||||
var sys = require('sys'),
|
||||
http = require('http'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
url = require('url'),
|
||||
events = require('events');
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
res.writeHead(200, {});
|
||||
sys.p('GET ' + req.url);
|
||||
var file = fs.createReadStream('.' + req.url);
|
||||
file.addListener('data', bind(res, res.write));
|
||||
file.addListener('error', function( error ){
|
||||
sys.p(error);
|
||||
res.end();
|
||||
});
|
||||
file.addListener('close', bind(res, res.end));
|
||||
}).listen(8000);
|
||||
sys.puts('Server running at http://127.0.0.1:8000/');
|
||||
var DEFAULT_PORT = 8000;
|
||||
|
||||
function bind(_this, _fn) {
|
||||
return function(){
|
||||
return _fn.apply(_this, arguments);
|
||||
};
|
||||
function main(argv) {
|
||||
new HttpServer({
|
||||
'GET': createServlet(StaticServlet),
|
||||
'HEAD': createServlet(StaticServlet)
|
||||
}).start(Number(argv[2]) || DEFAULT_PORT);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return value.toString().
|
||||
replace('<', '<').
|
||||
replace('>', '>').
|
||||
replace('"', '"');
|
||||
}
|
||||
|
||||
function createServlet(Class) {
|
||||
var servlet = new Class();
|
||||
return servlet.handleRequest.bind(servlet);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Http server implementation that uses a map of methods to decide
|
||||
* action routing.
|
||||
*
|
||||
* @param {Object} Map of method => Handler function
|
||||
*/
|
||||
function HttpServer(handlers) {
|
||||
this.handlers = handlers;
|
||||
this.server = http.createServer(this.handleRequest_.bind(this));
|
||||
}
|
||||
|
||||
HttpServer.prototype.start = function(port) {
|
||||
this.port = port;
|
||||
this.server.listen(port);
|
||||
sys.puts('Http Server running at http://127.0.0.1:' + port + '/');
|
||||
};
|
||||
|
||||
HttpServer.prototype.parseUrl_ = function(urlString) {
|
||||
var parsed = url.parse(urlString);
|
||||
parsed.pathname = url.resolve('/', parsed.pathname);
|
||||
return url.parse(url.format(parsed), true);
|
||||
};
|
||||
|
||||
HttpServer.prototype.handleRequest_ = function(req, res) {
|
||||
var logEntry = req.method + ' ' + req.url;
|
||||
if (req.headers['user-agent']) {
|
||||
logEntry += ' ' + req.headers['user-agent'];
|
||||
}
|
||||
sys.puts(logEntry);
|
||||
req.url = this.parseUrl_(req.url);
|
||||
var handler = this.handlers[req.method];
|
||||
if (!handler) {
|
||||
res.writeHead(501);
|
||||
res.end();
|
||||
} else {
|
||||
handler.call(this, req, res);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles static content.
|
||||
*/
|
||||
function StaticServlet() {}
|
||||
|
||||
StaticServlet.MimeMap = {
|
||||
'txt': 'text/plain',
|
||||
'html': 'text/html',
|
||||
'css': 'text/css',
|
||||
'xml': 'application/xml',
|
||||
'json': 'application/json',
|
||||
'js': 'application/javascript',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'gif': 'image/gif',
|
||||
'png': 'image/png'
|
||||
};
|
||||
|
||||
StaticServlet.prototype.handleRequest = function(req, res) {
|
||||
var self = this;
|
||||
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){
|
||||
return String.fromCharCode(parseInt(hex, 16));
|
||||
});
|
||||
var parts = path.split('/');
|
||||
if (parts[parts.length-1].charAt(0) === '.')
|
||||
return self.sendForbidden_(req, res, path);
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendMissing_(req, res, path);
|
||||
if (stat.isDirectory())
|
||||
return self.sendDirectory_(req, res, path);
|
||||
return self.sendFile_(req, res, path);
|
||||
});
|
||||
}
|
||||
|
||||
StaticServlet.prototype.sendError_ = function(req, res, error) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>Internal Server Error</title>\n');
|
||||
res.write('<h1>Internal Server Error</h1>');
|
||||
res.write('<pre>' + escapeHtml(sys.inspect(error)) + '</pre>');
|
||||
sys.puts('500 Internal Server Error');
|
||||
sys.puts(sys.inspect(error));
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>404 Not Found</title>\n');
|
||||
res.write('<h1>Not Found</h1>');
|
||||
res.write(
|
||||
'<p>The requested URL ' +
|
||||
escapeHtml(path) +
|
||||
' was not found on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('404 Not Found: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(403, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>403 Forbidden</title>\n');
|
||||
res.write('<h1>Forbidden</h1>');
|
||||
res.write(
|
||||
'<p>You do not have permission to access ' +
|
||||
escapeHtml(path) + ' on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('403 Forbidden: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
|
||||
res.writeHead(301, {
|
||||
'Content-Type': 'text/html',
|
||||
'Location': redirectUrl
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>301 Moved Permanently</title>\n');
|
||||
res.write('<h1>Moved Permanently</h1>');
|
||||
res.write(
|
||||
'<p>The document has moved <a href="' +
|
||||
redirectUrl +
|
||||
'">here</a>.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('401 Moved Permanently: ' + redirectUrl);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendFile_ = function(req, res, path) {
|
||||
var self = this;
|
||||
var file = fs.createReadStream(path);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': StaticServlet.
|
||||
MimeMap[path.split('.').pop()] || 'text/plain'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
} else {
|
||||
file.on('data', res.write.bind(res));
|
||||
file.on('close', function() {
|
||||
res.end();
|
||||
});
|
||||
file.on('error', function(error) {
|
||||
self.sendError_(req, res, error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
|
||||
var self = this;
|
||||
if (path.match(/[^\/]$/)) {
|
||||
req.url.pathname += '/';
|
||||
var redirectUrl = url.format(url.parse(url.format(req.url)));
|
||||
return self.sendRedirect_(req, res, redirectUrl);
|
||||
}
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, error);
|
||||
|
||||
if (!files.length)
|
||||
return self.writeDirectoryIndex_(req, res, path, []);
|
||||
|
||||
var remaining = files.length;
|
||||
files.forEach(function(fileName, index) {
|
||||
fs.stat(path + '/' + fileName, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, err);
|
||||
if (stat.isDirectory()) {
|
||||
files[index] = fileName + '/';
|
||||
}
|
||||
if (!(--remaining))
|
||||
return self.writeDirectoryIndex_(req, res, path, files);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>' + escapeHtml(path) + '</title>\n');
|
||||
res.write('<style>\n');
|
||||
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
|
||||
res.write('</style>\n');
|
||||
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
|
||||
res.write('<ol>');
|
||||
files.forEach(function(fileName) {
|
||||
if (fileName.charAt(0) !== '.') {
|
||||
res.write('<li><a href="' +
|
||||
escapeHtml(fileName) + '">' +
|
||||
escapeHtml(fileName) + '</a></li>');
|
||||
}
|
||||
});
|
||||
res.write('</ol>');
|
||||
res.end();
|
||||
};
|
||||
|
||||
// Must be last,
|
||||
main(process.argv);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
+1
-1
@@ -1 +1 @@
|
||||
node lib/nodeserver/server.js
|
||||
node lib/nodeserver/server.js $1
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</script>
|
||||
</head>
|
||||
<body ng:init="$window.$root = this; data = [{foo: 'foo'},{bar: 'bar'}]">
|
||||
<p>This is a demo of a potential bug in angular.</p>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</head>
|
||||
<body ng:init="$window.$root = this; data = [{foo: 'foo'},{bar: 'bar'}]">
|
||||
<ng:include src="'ng_include_this.partial'" scope="this"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
included HTML. eval count: {{c=c+1}}
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../angular-scenario.js"></script>
|
||||
<script type="text/javascript" src="../build/angular-scenario.js"></script>
|
||||
<script type="text/javascript" src="widgets-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/scenario/bootstrap.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="widgets-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('widgets', function() {
|
||||
it('should verify that basic widgets work', function(){
|
||||
navigateTo('widgets.html');
|
||||
browser().navigateTo('widgets.html');
|
||||
|
||||
using('#text-basic-box').input('text.basic').enter('Carlos');
|
||||
expect(binding('text.basic')).toEqual('Carlos');
|
||||
@@ -27,15 +27,29 @@ describe('widgets', function() {
|
||||
expect(binding('multiselect').fromJson()).toEqual(['A', 'C']);
|
||||
|
||||
expect(binding('button').fromJson()).toEqual({'count': 0});
|
||||
element('form a').click();
|
||||
expect(binding('form').fromJson()).toEqual({'count': 0});
|
||||
|
||||
element('form a', "'action' link").click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 1});
|
||||
element('input[value="submit"]').click();
|
||||
|
||||
element('input[value="submit input"]', "'submit input' button").click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 2});
|
||||
element('input[value="button"]').click();
|
||||
expect(binding('form').fromJson()).toEqual({'count': 1});
|
||||
|
||||
element('button:contains("submit button")', "'submit button' button").click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 2});
|
||||
expect(binding('form').fromJson()).toEqual({'count': 2});
|
||||
|
||||
element('input[value="button"]', "'button' button").click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 3});
|
||||
element('input[type="image"]').click();
|
||||
|
||||
element('input[type="image"]', 'form image').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 4});
|
||||
|
||||
element('#navigate a', "'Go to #route' link").click();
|
||||
expect(binding('$location.hash')).toEqual('route');
|
||||
expect(browser().location().hash()).toEqual('route');
|
||||
|
||||
/**
|
||||
* Custom value parser for futures.
|
||||
*/
|
||||
|
||||
+10
-5
@@ -2,7 +2,6 @@
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<script type="text/javascript" src="../libs/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</head>
|
||||
<body ng:init="$window.$scope = this">
|
||||
@@ -74,15 +73,16 @@
|
||||
<tr><th colspan="3">Buttons</th></tr>
|
||||
<tr>
|
||||
<td>ng:change<br/>ng:click</td>
|
||||
<td ng:init="button.count = 0">
|
||||
<form>
|
||||
<td ng:init="button.count = 0; form.count = 0;">
|
||||
<form ng:submit="form.count = form.count + 1">
|
||||
<input type="button" value="button" ng:change="button.count = button.count + 1"/> <br/>
|
||||
<input type="submit" value="submit" ng:change="button.count = button.count + 1"/><br/>
|
||||
<input type="submit" value="submit input" ng:change="button.count = button.count + 1"/><br/>
|
||||
<button type="submit">submit button</button>
|
||||
<input type="image" src="" ng:change="button.count = button.count + 1"/><br/>
|
||||
<a href="" ng:click="button.count = button.count + 1">action</a>
|
||||
</form>
|
||||
</td>
|
||||
<td>button={{button}}</td>
|
||||
<td>button={{button}} form={{form}}</td>
|
||||
</tr>
|
||||
<tr><th colspan="3">Repeaters</th></tr>
|
||||
<tr id="repeater-row">
|
||||
@@ -94,6 +94,11 @@
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id="navigate">
|
||||
<td>navigate</td>
|
||||
<td><a href="#route">Go to #route</td>
|
||||
<td>{{$location.hash}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +1 @@
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000
|
||||
|
||||
+148
-22
@@ -3,6 +3,51 @@
|
||||
if (typeof document.getAttribute == $undefined)
|
||||
document.getAttribute = function() {};
|
||||
|
||||
/**
|
||||
* @ngdoc
|
||||
* @name angular.lowercase
|
||||
* @function
|
||||
*
|
||||
* @description Converts string to lowercase
|
||||
* @param {string} value
|
||||
* @returns {string} Lowercased string.
|
||||
*/
|
||||
var lowercase = function (value){ return isString(value) ? value.toLowerCase() : value; };
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc
|
||||
* @name angular#uppercase
|
||||
* @function
|
||||
*
|
||||
* @description Converts string to uppercase.
|
||||
* @param {string} value
|
||||
* @returns {string} Uppercased string.
|
||||
*/
|
||||
var uppercase = function (value){ return isString(value) ? value.toUpperCase() : value; };
|
||||
|
||||
|
||||
var manualLowercase = function (s) {
|
||||
return isString(s) ? s.replace(/[A-Z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) | 32); }) : s;
|
||||
};
|
||||
var manualUppercase = function (s) {
|
||||
return isString(s) ? s.replace(/[a-z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) & ~32); }) : s;
|
||||
};
|
||||
|
||||
|
||||
// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
|
||||
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods with
|
||||
// correct but slower alternatives.
|
||||
if ('i' !== 'I'.toLowerCase()) {
|
||||
lowercase = manualLowercase;
|
||||
uppercase = manualUppercase;
|
||||
}
|
||||
|
||||
function fromCharCode(code) { return String.fromCharCode(code); }
|
||||
|
||||
|
||||
var _undefined = undefined,
|
||||
_null = null,
|
||||
$$element = '$element',
|
||||
@@ -38,12 +83,99 @@ var _undefined = undefined,
|
||||
slice = Array.prototype.slice,
|
||||
push = Array.prototype.push,
|
||||
error = window[$console] ? bind(window[$console], window[$console]['error'] || noop) : noop,
|
||||
|
||||
/**
|
||||
* @name angular
|
||||
* @namespace The exported angular namespace.
|
||||
*/
|
||||
angular = window[$angular] || (window[$angular] = {}),
|
||||
angularTextMarkup = extensionMap(angular, 'markup'),
|
||||
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
|
||||
angularDirective = extensionMap(angular, 'directive'),
|
||||
angularWidget = extensionMap(angular, 'widget', lowercase),
|
||||
angularValidator = extensionMap(angular, 'validator'),
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name angular.filter
|
||||
* @namespace Namespace for all filters.
|
||||
* @description
|
||||
* # Overview
|
||||
* Filters are a standard way to format your data for display to the user. For example, you
|
||||
* might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
|
||||
* Filters allow you to do just that. In addition to transforming the data, filters also modify
|
||||
* the DOM. This allows the filters to for example apply css styles to the filtered output if
|
||||
* certain conditions were met.
|
||||
*
|
||||
*
|
||||
* # Standard Filters
|
||||
*
|
||||
* The Angular framework provides a standard set of filters for common operations, including:
|
||||
* {@link angular.filter.currency}, {@link angular.filter.json}, {@link angular.filter.number},
|
||||
* and {@link angular.filter.html}. You can also add your own filters.
|
||||
*
|
||||
*
|
||||
* # Syntax
|
||||
*
|
||||
* Filters can be part of any {@link angular.scope} evaluation but are typically used with
|
||||
* {{bindings}}. Filters typically transform the data to a new data type, formating the data in
|
||||
* the process. Filters can be chained and take optional arguments. Here are few examples:
|
||||
*
|
||||
* * No filter: {{1234.5678}} => 1234.5678
|
||||
* * Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
|
||||
* significant digits.
|
||||
* * Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
|
||||
* arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
|
||||
* to the right of the decimal point.
|
||||
*
|
||||
*
|
||||
* # Writing your own Filters
|
||||
*
|
||||
* Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
|
||||
* The framework passes in the input value as the first argument to your function. Any filter
|
||||
* arguments are passed in as additional function arguments.
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* 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).
|
||||
|
||||
<script type="text/javascript">
|
||||
angular.filter.reverse = function(input, uppercase, color) {
|
||||
var out = "";
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
out = input.charAt(i) + out;
|
||||
}
|
||||
if (uppercase) {
|
||||
out = out.toUpperCase();
|
||||
}
|
||||
if (color) {
|
||||
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.
|
||||
*/
|
||||
angularFilter = extensionMap(angular, 'filter'),
|
||||
angularFormatter = extensionMap(angular, 'formatter'),
|
||||
angularService = extensionMap(angular, 'service'),
|
||||
@@ -134,15 +266,26 @@ function isNumber(value){ return typeof value == $number;}
|
||||
function isArray(value) { return value instanceof Array; }
|
||||
function isFunction(value){ return typeof value == $function;}
|
||||
function isTextNode(node) { return nodeName(node) == '#text'; }
|
||||
function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }
|
||||
function uppercase(value){ return isString(value) ? value.toUpperCase() : value; }
|
||||
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
|
||||
function isElement(node) {
|
||||
return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery));
|
||||
}
|
||||
|
||||
function HTML(html) {
|
||||
/**
|
||||
* HTML class which is the only class which can be used in ng:bind to inline HTML for security reasons.
|
||||
* @constructor
|
||||
* @param html raw (unsafe) html
|
||||
* @param {string=} option if set to 'usafe' then get method will return raw (unsafe/unsanitized) html
|
||||
*/
|
||||
function HTML(html, option) {
|
||||
this.html = html;
|
||||
this.get = lowercase(option) == 'unsafe' ?
|
||||
valueFn(html) :
|
||||
function htmlSanitize() {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
}
|
||||
|
||||
if (msie) {
|
||||
@@ -225,7 +368,7 @@ function isLeafNode (node) {
|
||||
* @param {*} source The source to be used during copy.
|
||||
* Can be any type including primitives, null and undefined.
|
||||
* @param {(Object|Array)=} destination Optional destination into which the source is copied
|
||||
* @return {*}
|
||||
* @returns {*}
|
||||
*/
|
||||
function copy(source, destination){
|
||||
if (!destination) {
|
||||
@@ -297,16 +440,6 @@ function setHtml(node, html) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(html) {
|
||||
if (!html || !html.replace)
|
||||
return html;
|
||||
return html.
|
||||
replace(/&/g, '&').
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
|
||||
function isRenderableElement(element) {
|
||||
var name = element && element[0] && element[0].nodeName;
|
||||
return name && name.charAt(0) != '#' &&
|
||||
@@ -328,13 +461,6 @@ function elementError(element, type, error) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttr(html) {
|
||||
if (!html || !html.replace)
|
||||
return html;
|
||||
return html.replace(/</g, '<').replace(/>/g, '>').replace(/\"/g,
|
||||
'"');
|
||||
}
|
||||
|
||||
function concat(array1, array2, index) {
|
||||
return array1.concat(slice.call(array2, index, array2.length));
|
||||
}
|
||||
@@ -385,7 +511,7 @@ function compile(element, existingScope) {
|
||||
|
||||
/**
|
||||
* Parses an escaped url query string into key-value pairs.
|
||||
* @return Object.<(string|boolean)>
|
||||
* @returns Object.<(string|boolean)>
|
||||
*/
|
||||
function parseKeyValue(/**string*/keyValue) {
|
||||
var obj = {}, key_value, key;
|
||||
|
||||
+11
-2
@@ -112,9 +112,11 @@ Compiler.prototype = {
|
||||
templatize: function(element, elementIndex, priority){
|
||||
var self = this,
|
||||
widget,
|
||||
fn,
|
||||
directiveFns = self.directives,
|
||||
descend = true,
|
||||
directives = true,
|
||||
elementName = nodeName(element),
|
||||
template,
|
||||
selfApi = {
|
||||
compile: bind(self, self.compile),
|
||||
@@ -138,12 +140,15 @@ Compiler.prototype = {
|
||||
eachAttribute(element, function(value, name){
|
||||
if (!widget) {
|
||||
if (widget = self.widgets('@' + name)) {
|
||||
element.addClass('ng-attr-widget');
|
||||
widget = bind(selfApi, widget, value, element);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!widget) {
|
||||
if (widget = self.widgets(nodeName(element))) {
|
||||
if (widget = self.widgets(elementName)) {
|
||||
if (elementName.indexOf(':') > 0)
|
||||
element.addClass('ng-widget');
|
||||
widget = bind(selfApi, widget, element);
|
||||
}
|
||||
}
|
||||
@@ -179,7 +184,11 @@ Compiler.prototype = {
|
||||
});
|
||||
});
|
||||
eachAttribute(element, function(value, name){
|
||||
template.addInit((directiveFns[name]||noop).call(selfApi, value, element));
|
||||
fn = directiveFns[name];
|
||||
if (fn) {
|
||||
element.addClass('ng-directive');
|
||||
template.addInit((directiveFns[name]).call(selfApi, value, element));
|
||||
}
|
||||
});
|
||||
}
|
||||
// Process non text child nodes
|
||||
|
||||
+3
-1
@@ -33,6 +33,8 @@ function toJsonArray(buf, obj, pretty, stack){
|
||||
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) {
|
||||
return;
|
||||
} else if (type === $boolean) {
|
||||
@@ -53,7 +55,7 @@ function toJsonArray(buf, obj, pretty, stack){
|
||||
for(var i=0; i<len; i++) {
|
||||
var item = obj[i];
|
||||
if (sep) buf.push(",");
|
||||
if (typeof item == $function || typeof item == $undefined) {
|
||||
if (!(item instanceof RegExp) && (typeof item == $function || typeof item == $undefined)) {
|
||||
buf.push($null);
|
||||
} else {
|
||||
toJsonArray(buf, item, pretty, stack);
|
||||
|
||||
+5
-5
@@ -49,11 +49,11 @@ var scopeId = 0,
|
||||
compileCache = {},
|
||||
JS_KEYWORDS = {};
|
||||
foreach(
|
||||
["abstract", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default",
|
||||
"delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", $function, "goto",
|
||||
"if", "implements", "import", "ininstanceof", "intinterface", "long", "native", "new", $null, "package", "private",
|
||||
"protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws",
|
||||
"transient", "true", "try", "typeof", "var", "volatile", "void", $undefined, "while", "with"],
|
||||
("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
|
||||
"delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
|
||||
"if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," +
|
||||
"protected,public,return,short,static,super,switch,synchronized,this,throw,throws," +
|
||||
"transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/),
|
||||
function(key){ JS_KEYWORDS[key] = true;}
|
||||
);
|
||||
function getterFn(path){
|
||||
|
||||
Vendored
+1
@@ -53,6 +53,7 @@
|
||||
addScript("/parser.js");
|
||||
addScript("/Resource.js");
|
||||
addScript("/Browser.js");
|
||||
addScript("/sanitizer.js");
|
||||
addScript("/AngularPublic.js");
|
||||
|
||||
// Extension points
|
||||
|
||||
@@ -202,6 +202,12 @@ var angularString = {
|
||||
}
|
||||
return chars.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to convert input to date and if successful returns the date, otherwise returns the input.
|
||||
* @param {string} string
|
||||
* @return {(Date|string)}
|
||||
*/
|
||||
'toDate':function(string){
|
||||
var match;
|
||||
if (typeof string == 'string' &&
|
||||
|
||||
+31
-6
@@ -22,19 +22,24 @@ angularDirective("ng:eval", function(expression){
|
||||
};
|
||||
});
|
||||
|
||||
angularDirective("ng:bind", function(expression){
|
||||
angularDirective("ng:bind", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
return function(element) {
|
||||
var lastValue = noop, lastError = noop;
|
||||
this.$onEval(function() {
|
||||
var error, value, isHtml, isDomElement,
|
||||
var error, value, html, isHtml, isDomElement,
|
||||
oldElement = this.hasOwnProperty($$element) ? this.$element : _undefined;
|
||||
this.$element = element;
|
||||
value = this.$tryEval(expression, function(e){
|
||||
error = toJson(e);
|
||||
});
|
||||
this.$element = oldElement;
|
||||
// If we are HTML than save the raw HTML data so that we don't
|
||||
// recompute sanitization since it is expensive.
|
||||
// TODO: turn this into a more generic way to compute this
|
||||
if (isHtml = (value instanceof HTML))
|
||||
value = (html = value).html;
|
||||
if (lastValue === value && lastError == error) return;
|
||||
isHtml = value instanceof HTML;
|
||||
isDomElement = isElement(value);
|
||||
if (!isHtml && !isDomElement && isObject(value)) {
|
||||
value = toJson(value);
|
||||
@@ -45,7 +50,7 @@ angularDirective("ng:bind", function(expression){
|
||||
elementError(element, NG_EXCEPTION, error);
|
||||
if (error) value = error;
|
||||
if (isHtml) {
|
||||
element.html(value.html);
|
||||
element.html(html.get());
|
||||
} else if (isDomElement) {
|
||||
element.html('');
|
||||
element.append(value);
|
||||
@@ -93,7 +98,8 @@ function compileBindTemplate(template){
|
||||
return fn;
|
||||
}
|
||||
|
||||
angularDirective("ng:bind-template", function(expression){
|
||||
angularDirective("ng:bind-template", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
var templateFn = compileBindTemplate(expression);
|
||||
return function(element) {
|
||||
var lastValue;
|
||||
@@ -205,7 +211,7 @@ angularWidget("@ng:repeat", function(expression, element){
|
||||
*
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
*
|
||||
* TODO: maybe we should consider allowing users to control even propagation in the future.
|
||||
* TODO: maybe we should consider allowing users to control event propagation in the future.
|
||||
*/
|
||||
angularDirective("ng:click", function(expression, element){
|
||||
return function(element){
|
||||
@@ -218,6 +224,25 @@ angularDirective("ng:click", function(expression, element){
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Enables binding angular expressions to onsubmit events.
|
||||
*
|
||||
* Additionally it prevents the default action (which for form means sending the request to the
|
||||
* server and reloading the current page).
|
||||
*/
|
||||
angularDirective("ng:submit", function(expression, element) {
|
||||
return function(element) {
|
||||
var self = this;
|
||||
element.bind('submit', function(event) {
|
||||
self.$tryEval(expression, element);
|
||||
self.$root.$eval();
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
angularDirective("ng:watch", function(expression, element){
|
||||
return function(element){
|
||||
var self = this;
|
||||
|
||||
+343
-23
@@ -1,17 +1,74 @@
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.currency
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Formats a number as a currency (ie $1,234.56).
|
||||
*
|
||||
* @param {number} amount Input to filter.
|
||||
* @returns {string} Formated number.
|
||||
*
|
||||
* @css ng-format-negative
|
||||
* When the value is negative, this css class is applied to the binding making it by default red.
|
||||
*
|
||||
* @example
|
||||
<input type="text" name="amount" value="1234.56"/> <br/>
|
||||
{{amount | currency}}
|
||||
*
|
||||
* @scenario
|
||||
it('should init with 1234.56', function(){
|
||||
expect(binding('amount | currency')).toBe('$1,234.56');
|
||||
});
|
||||
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?
|
||||
});
|
||||
*/
|
||||
angularFilter.currency = function(amount){
|
||||
this.$element.toggleClass('ng-format-negative', amount < 0);
|
||||
return '$' + angularFilter['number'].apply(this, [amount, 2]);
|
||||
};
|
||||
|
||||
angularFilter.number = function(amount, fractionSize){
|
||||
if (isNaN(amount) || !isFinite(amount)) {
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.number
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Formats a number as text.
|
||||
*
|
||||
* If the input is not a number empty string is returned.
|
||||
*
|
||||
* @param {(number|string)} number Number to format.
|
||||
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. Default 2.
|
||||
* @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}}
|
||||
*
|
||||
* @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');
|
||||
});
|
||||
*/
|
||||
angularFilter.number = function(number, fractionSize){
|
||||
if (isNaN(number) || !isFinite(number)) {
|
||||
return '';
|
||||
}
|
||||
fractionSize = typeof fractionSize == $undefined ? 2 : fractionSize;
|
||||
var isNegative = amount < 0;
|
||||
amount = Math.abs(amount);
|
||||
var isNegative = number < 0;
|
||||
number = Math.abs(number);
|
||||
var pow = Math.pow(10, fractionSize);
|
||||
var text = "" + Math.round(amount * pow);
|
||||
var text = "" + Math.round(number * pow);
|
||||
var whole = text.substring(0, text.length - fractionSize);
|
||||
whole = whole || '0';
|
||||
var frc = text.substring(text.length - fractionSize);
|
||||
@@ -30,6 +87,8 @@ angularFilter.number = function(amount, fractionSize){
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
function padNumber(num, digits, trim) {
|
||||
var neg = '';
|
||||
if (num < 0) {
|
||||
@@ -42,15 +101,19 @@ function padNumber(num, digits, trim) {
|
||||
num = num.substr(num.length - digits);
|
||||
return neg + num;
|
||||
}
|
||||
|
||||
|
||||
function dateGetter(name, size, offset, trim) {
|
||||
return function(date) {
|
||||
var value = date['get' + name].call(date);
|
||||
var value = date['get' + name]();
|
||||
if (offset > 0 || value > -offset)
|
||||
value += offset;
|
||||
if (value === 0 && offset == -12 ) value = 12;
|
||||
return padNumber(value, size, trim);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var DATE_FORMATS = {
|
||||
yyyy: dateGetter('FullYear', 4),
|
||||
yy: dateGetter('FullYear', 2, 0, true),
|
||||
@@ -66,16 +129,75 @@ var DATE_FORMATS = {
|
||||
m: dateGetter('Minutes', 1),
|
||||
ss: dateGetter('Seconds', 2),
|
||||
s: dateGetter('Seconds', 1),
|
||||
a: function(date){return date.getHours() < 12 ? 'am' : 'pm'; },
|
||||
a: function(date){return date.getHours() < 12 ? 'am' : 'pm';},
|
||||
Z: function(date){
|
||||
var offset = date.getTimezoneOffset();
|
||||
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
|
||||
}
|
||||
};
|
||||
var DATE_FORMATS_SPLIT = /([^yMdHhmsaZ]*)(y+|M+|d+|H+|h+|m+|s+|a|Z)(.*)/;
|
||||
|
||||
|
||||
var DATE_FORMATS_SPLIT = /([^yMdHhmsaZ]*)(y+|M+|d+|H+|h+|m+|s+|a|Z)(.*)/;
|
||||
var NUMBER_STRING = /^\d+$/;
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.date
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Formats `date` to a string based on the requested `format`.
|
||||
*
|
||||
* `format` string can be composed of the following elements:
|
||||
*
|
||||
* * `'yyyy'`: 4 digit representation of year e.g. 2010
|
||||
* * `'yy'`: 2 digit representation of year, padded (00-99)
|
||||
* * `'MM'`: Month in year, padded (01‒12)
|
||||
* * `'M'`: Month in year (1‒12)
|
||||
* * `'dd'`: Day in month, padded (01‒31)
|
||||
* * `'d'`: Day in month (1-31)
|
||||
* * `'HH'`: Hour in day, padded (00‒23)
|
||||
* * `'H'`: Hour in day (0-23)
|
||||
* * `'hh'`: Hour in am/pm, padded (01‒12)
|
||||
* * `'h'`: Hour in am/pm, (1-12)
|
||||
* * `'mm'`: Minute in hour, padded (00‒59)
|
||||
* * `'m'`: Minute in hour (0-59)
|
||||
* * `'ss'`: Second in minute, padded (00‒59)
|
||||
* * `'s'`: Second in minute (0‒59)
|
||||
* * `'a'`: am/pm marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200‒1200)
|
||||
*
|
||||
* @param {(Date|number|string)} date Date to format either as Date object or milliseconds.
|
||||
* @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.
|
||||
*
|
||||
* @example
|
||||
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
|
||||
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
|
||||
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
|
||||
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should format date', function(){
|
||||
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
||||
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/);
|
||||
});
|
||||
*
|
||||
*/
|
||||
angularFilter.date = function(date, format) {
|
||||
if (!(date instanceof Date)) return date;
|
||||
if (isString(date) && NUMBER_STRING.test(date)) {
|
||||
date = parseInt(date, 10);
|
||||
}
|
||||
|
||||
if (isNumber(date)) {
|
||||
date = new Date(date);
|
||||
} else if (!(date instanceof Date)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
var text = date.toLocaleDateString(), fn;
|
||||
if (format && isString(format)) {
|
||||
text = '';
|
||||
@@ -92,37 +214,235 @@ angularFilter.date = function(date, format) {
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.json
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Allows you to convert a JavaScript object into JSON string.
|
||||
*
|
||||
* This filter is mostly useful for debugging. When using the double curly {{value}} notation
|
||||
* the binding is automatically converted to JSON.
|
||||
*
|
||||
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
|
||||
* @returns {string} JSON string.
|
||||
*
|
||||
* @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>
|
||||
*
|
||||
* @scenario
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(binding('{{ {a:1, b:[]} | json')).toBe('{\n "a":1,\n "b":[]}');
|
||||
});
|
||||
*
|
||||
*/
|
||||
angularFilter.json = function(object) {
|
||||
this.$element.addClass("ng-monospace");
|
||||
return toJson(object, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.lowercase
|
||||
* @function
|
||||
*
|
||||
* @see angular.lowercase
|
||||
*/
|
||||
angularFilter.lowercase = lowercase;
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.uppercase
|
||||
* @function
|
||||
*
|
||||
* @see angular.uppercase
|
||||
*/
|
||||
angularFilter.uppercase = uppercase;
|
||||
|
||||
angularFilter.html = function(html){
|
||||
return new HTML(html);
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.html
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Prevents the input from getting escaped by angular. By default the input is sanitized and
|
||||
* inserted into the DOM as is.
|
||||
*
|
||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
||||
* it into the returned string, however since our parser is more strict than a typical browser
|
||||
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
||||
* browser, won't make it through the sanitizer.
|
||||
*
|
||||
* If you hate your users, you may call the filter with optional 'unsafe' argument, which bypasses
|
||||
* the html sanitizer, but makes your application vulnerable to XSS and other attacks. Using this
|
||||
* option is strongly discouraged and should be used only if you absolutely trust the input being
|
||||
* filtered and you can't get the content through the sanitizer.
|
||||
*
|
||||
* @param {string} html Html input.
|
||||
* @param {string=} option If 'unsafe' then do not sanitize the HTML input.
|
||||
* @returns {string} Sanitized or raw html.
|
||||
*
|
||||
* @example
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
<p style="color:blue">an html
|
||||
<em onmouseover="this.textContent='PWN3D!'">click here</em>
|
||||
snippet</p></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="html-filter">
|
||||
<td>html filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | html"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | html"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng:bind="snippet | html:'unsafe'"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet | html:'unsafe'"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
it('should sanitize the html snippet ', function(){
|
||||
expect(using('#html-filter').binding('snippet | html')).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
|
||||
it ('should escape snippet without any filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it ('should inline raw snippet if filtered as unsafe', function() {
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
textarea('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 <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
|
||||
});
|
||||
*/
|
||||
angularFilter.html = function(html, option){
|
||||
return new HTML(html, option);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name angular.filter.linky
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
||||
* plane email address links.
|
||||
*
|
||||
* @param {string} text Input text.
|
||||
* @returns {string} Html-linkified text.
|
||||
*
|
||||
* @example
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
Pretty text with some links:
|
||||
http://angularjs.org/,
|
||||
mailto:us@somewhere.org,
|
||||
another@somewhere.org,
|
||||
and one more: ftp://127.0.0.1/.</textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="linky-filter">
|
||||
<td>linky filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | linky"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
it('should linkify the snippet with urls', function(){
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links:\n' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' +
|
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
||||
});
|
||||
|
||||
it ('should not linkify snippet without the linky filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("Pretty text with some links:\n" +
|
||||
"http://angularjs.org/,\n" +
|
||||
"mailto:us@somewhere.org,\n" +
|
||||
"another@somewhere.org,\n" +
|
||||
"and one more: ftp://127.0.0.1/.");
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
textarea('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.');
|
||||
});
|
||||
*/
|
||||
//TODO: externalize all regexps
|
||||
angularFilter.linky = function(text){
|
||||
if (!text) return text;
|
||||
function regExpEscape(text) {
|
||||
return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1');
|
||||
}
|
||||
var URL = /(ftp|http|https|mailto):\/\/([^\(\)|\s]+)/;
|
||||
var URL = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/;
|
||||
var match;
|
||||
var raw = text;
|
||||
var html = [];
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
while (match=raw.match(URL)) {
|
||||
var url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
|
||||
var i = raw.indexOf(url);
|
||||
html.push(escapeHtml(raw.substr(0, i)));
|
||||
html.push('<a href="' + url + '">');
|
||||
html.push(url);
|
||||
html.push('</a>');
|
||||
raw = raw.substring(i + url.length);
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2]==match[3]) url = 'mailto:' + url;
|
||||
i = match.index;
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
writer.chars(match[0].replace(/^mailto:/, ''));
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + match[0].length);
|
||||
}
|
||||
html.push(escapeHtml(raw));
|
||||
writer.chars(raw);
|
||||
return new HTML(html.join(''));
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ function lex(text, parseStringsForObjects){
|
||||
}
|
||||
function isWhitespace(ch) {
|
||||
return ch == ' ' || ch == '\r' || ch == '\t' ||
|
||||
ch == '\n' || ch == '\v';
|
||||
ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
|
||||
}
|
||||
function isIdent(ch) {
|
||||
return 'a' <= ch && ch <= 'z' ||
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
||||
* based on: HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:]+)[^>]*>/,
|
||||
ATTR_REGEXP = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var emptyElements = makeMap("area,base,basefont,br,col,hr,img,input,isindex,link,param");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var blockElements = makeMap("address,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,"+
|
||||
"form,hr,ins,isindex,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inlineElements = makeMap("a,abbr,acronym,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,img,"+
|
||||
"input,ins,kbd,label,map,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelfElements = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
||||
var validElements = extend({}, emptyElements, blockElements, inlineElements, closeSelfElements);
|
||||
var validAttrs = extend({}, fillAttrs, makeMap(
|
||||
'abbr,align,alink,alt,archive,axis,background,bgcolor,border,cellpadding,cellspacing,cite,class,classid,clear,code,codebase,'+
|
||||
'codetype,color,cols,colspan,content,coords,data,dir,face,for,headers,height,href,hreflang,hspace,id,label,lang,language,'+
|
||||
'link,longdesc,marginheight,marginwidth,maxlength,media,method,name,nowrap,profile,prompt,rel,rev,rows,rowspan,rules,scheme,'+
|
||||
'scope,scrolling,shape,size,span,src,standby,start,summary,tabindex,target,text,title,type,usemap,valign,value,valuetype,'+
|
||||
'vlink,vspace,width'));
|
||||
|
||||
/**
|
||||
* @example
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* @param {string} html string
|
||||
* @param {object} handler
|
||||
*/
|
||||
var htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){ return stack[ stack.length - 1 ]; };
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") === 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
||||
match = html.match( END_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
||||
match = html.match( START_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
|
||||
text = text.
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last ) {
|
||||
throw "Parse Error: " + html;
|
||||
}
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
tagName = lowercase(tagName);
|
||||
if ( blockElements[ tagName ] ) {
|
||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelfElements[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = emptyElements[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs[name] = value; //value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
var pos = 0, i;
|
||||
tagName = lowercase(tagName);
|
||||
if ( tagName )
|
||||
// Find the closest opened tag of the same type
|
||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param str 'key1,key2,...'
|
||||
* @returns {object} in the form of {key1:true, key2:true, ...}
|
||||
*/
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(","), i;
|
||||
for ( i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* For attack vectors see: http://ha.ckers.org/xss.html
|
||||
*/
|
||||
var JAVASCRIPT_URL = /^javascript:/i,
|
||||
NBSP_REGEXP = / /gim,
|
||||
HEX_ENTITY_REGEXP = /&#x([\da-f]*);?/igm,
|
||||
DEC_ENTITY_REGEXP = /&#(\d+);?/igm,
|
||||
CHAR_REGEXP = /[\w:]/gm,
|
||||
HEX_DECODE = function(match, code){return fromCharCode(parseInt(code,16));},
|
||||
DEC_DECODE = function(match, code){return fromCharCode(code);};
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns true if url decodes to something which starts with 'javascript:' hence unsafe
|
||||
*/
|
||||
function isJavaScriptUrl(url) {
|
||||
var chars = [];
|
||||
url.replace(NBSP_REGEXP, '').
|
||||
replace(HEX_ENTITY_REGEXP, HEX_DECODE).
|
||||
replace(DEC_ENTITY_REGEXP, DEC_DECODE).
|
||||
// Remove all non \w: characters, unfurtunetly value.replace(/[\w:]/,'') can be defeated using \u0000
|
||||
replace(CHAR_REGEXP, function(ch){chars.push(ch);});
|
||||
return JAVASCRIPT_URL.test(lowercase(chars.join('')));
|
||||
}
|
||||
|
||||
/**
|
||||
* create an HTML/XML writer which writes to buffer
|
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
||||
* @returns {object} in the form of {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* }
|
||||
*/
|
||||
function htmlSanitizeWriter(buf){
|
||||
var ignore = false;
|
||||
var out = bind(buf, buf.push);
|
||||
return {
|
||||
start: function(tag, attrs, unary){
|
||||
tag = lowercase(tag);
|
||||
if (!ignore && specialElements[tag]) {
|
||||
ignore = tag;
|
||||
}
|
||||
if (!ignore && validElements[tag]) {
|
||||
out('<');
|
||||
out(tag);
|
||||
foreach(attrs, function(value, key){
|
||||
if (validAttrs[lowercase(key)] && !isJavaScriptUrl(value)) {
|
||||
out(' ');
|
||||
out(key);
|
||||
out('="');
|
||||
out(value.
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>').
|
||||
replace(/\"/g,'"'));
|
||||
out('"');
|
||||
}
|
||||
});
|
||||
out(unary ? '/>' : '>');
|
||||
}
|
||||
},
|
||||
end: function(tag){
|
||||
tag = lowercase(tag);
|
||||
if (!ignore && validElements[tag]) {
|
||||
out('</');
|
||||
out(tag);
|
||||
out('>');
|
||||
}
|
||||
if (tag == ignore) {
|
||||
ignore = false;
|
||||
}
|
||||
},
|
||||
chars: function(chars){
|
||||
if (!ignore) {
|
||||
out(chars.
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity){return entity?match:'&';}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>'));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+96
-17
@@ -1,51 +1,130 @@
|
||||
/**
|
||||
* Represents the application currently being tested and abstracts usage
|
||||
* of iframes or separate windows.
|
||||
*
|
||||
* @param {Object} context jQuery wrapper around HTML context.
|
||||
*/
|
||||
angular.scenario.Application = function(context) {
|
||||
this.context = context;
|
||||
context.append('<h2>Current URL: <a href="about:blank">None</a></h2>');
|
||||
context.append(
|
||||
'<h2>Current URL: <a href="about:blank">None</a></h2>' +
|
||||
'<div id="test-frames"></div>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the jQuery collection of frames. Don't use this directly because
|
||||
* frames may go stale.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} jQuery collection
|
||||
*/
|
||||
angular.scenario.Application.prototype.getFrame = function() {
|
||||
return this.context.find('> iframe');
|
||||
angular.scenario.Application.prototype.getFrame_ = function() {
|
||||
return this.context.find('#test-frames iframe:last');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* instead of this method since it prevents you from getting a stale window.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} the window of the frame
|
||||
*/
|
||||
angular.scenario.Application.prototype.getWindow = function() {
|
||||
var contentWindow = this.getFrame().attr('contentWindow');
|
||||
angular.scenario.Application.prototype.getWindow_ = function() {
|
||||
var contentWindow = this.getFrame_().attr('contentWindow');
|
||||
if (!contentWindow)
|
||||
throw 'No window available because frame not loaded.';
|
||||
throw 'Frame window is not accessible.';
|
||||
return contentWindow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the location of the frame.
|
||||
* Checks that a URL would return a 2xx success status code. Callback is called
|
||||
* with no arguments on success, or with an error on failure.
|
||||
*
|
||||
* Warning: This requires the server to be able to respond to HEAD requests
|
||||
* and not modify the state of your application.
|
||||
*
|
||||
* @param {string} url Url to check
|
||||
* @param {Function} callback function(error) that is called with result.
|
||||
*/
|
||||
angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {
|
||||
this.getFrame().remove();
|
||||
this.context.append('<iframe src=""></iframe>');
|
||||
this.context.find('> h2 a').attr('href', url).text(url);
|
||||
this.getFrame().attr('src', url).load(onloadFn);
|
||||
angular.scenario.Application.prototype.checkUrlStatus_ = function(url, callback) {
|
||||
var self = this;
|
||||
_jQuery.ajax({
|
||||
url: url,
|
||||
type: 'HEAD',
|
||||
complete: function(request) {
|
||||
if (request.status < 200 || request.status >= 300) {
|
||||
if (!request.status) {
|
||||
callback.call(self, 'Sandbox Error: Cannot access ' + url);
|
||||
} else {
|
||||
callback.call(self, request.status + ' ' + request.statusText);
|
||||
}
|
||||
} else {
|
||||
callback.call(self);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a function in the context of the tested application.
|
||||
* Changes the location of the frame.
|
||||
*
|
||||
* @param {Function} The callback to execute. function($window, $document)
|
||||
* @param {string} url The URL. If it begins with a # then only the
|
||||
* hash of the page is changed.
|
||||
* @param {Function} loadFn function($window, $document) Called when frame loads.
|
||||
* @param {Function} errorFn function(error) Called if any error when loading.
|
||||
*/
|
||||
angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorFn) {
|
||||
var self = this;
|
||||
var frame = this.getFrame_();
|
||||
//TODO(esprehn): Refactor to use rethrow()
|
||||
errorFn = errorFn || function(e) { throw e; };
|
||||
if (url === 'about:blank') {
|
||||
errorFn('Sandbox Error: Navigating to about:blank is not allowed.');
|
||||
} else if (url.charAt(0) === '#') {
|
||||
url = frame.attr('src').split('#')[0] + url;
|
||||
frame.attr('src', url);
|
||||
this.executeAction(loadFn);
|
||||
} else {
|
||||
frame.css('display', 'none').attr('src', 'about:blank');
|
||||
this.checkUrlStatus_(url, function(error) {
|
||||
if (error) {
|
||||
return errorFn(error);
|
||||
}
|
||||
self.context.find('#test-frames').append('<iframe>');
|
||||
frame = this.getFrame_();
|
||||
frame.load(function() {
|
||||
frame.unbind();
|
||||
try {
|
||||
self.executeAction(loadFn);
|
||||
} catch (e) {
|
||||
errorFn(e);
|
||||
}
|
||||
}).attr('src', url);
|
||||
});
|
||||
}
|
||||
this.context.find('> h2 a').attr('href', url).text(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a function in the context of the tested application. Will wait
|
||||
* for all pending angular xhr requests before executing.
|
||||
*
|
||||
* @param {Function} action The callback to execute. function($window, $document)
|
||||
* $document is a jQuery wrapped document.
|
||||
*/
|
||||
angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
var $window = this.getWindow();
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
var self = this;
|
||||
var $window = this.getWindow_();
|
||||
if (!$window.document) {
|
||||
throw 'Sandbox Error: Application document not accessible.';
|
||||
}
|
||||
if (!$window.angular) {
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
}
|
||||
var $browser = $window.angular.service.$browser();
|
||||
$browser.poll();
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
action.call(self, $window, _jQuery($window.document));
|
||||
});
|
||||
};
|
||||
|
||||
+54
-11
@@ -1,8 +1,12 @@
|
||||
/**
|
||||
* The representation of define blocks. Don't used directly, instead use
|
||||
* define() in your tests.
|
||||
*
|
||||
* @param {string} descName Name of the block
|
||||
* @param {Object} parent describe or undefined if the root.
|
||||
*/
|
||||
angular.scenario.Describe = function(descName, parent) {
|
||||
this.only = parent && parent.only;
|
||||
this.beforeEachFns = [];
|
||||
this.afterEachFns = [];
|
||||
this.its = [];
|
||||
@@ -10,7 +14,7 @@ angular.scenario.Describe = function(descName, parent) {
|
||||
this.name = descName;
|
||||
this.parent = parent;
|
||||
this.id = angular.scenario.Describe.id++;
|
||||
|
||||
|
||||
/**
|
||||
* Calls all before functions.
|
||||
*/
|
||||
@@ -36,7 +40,7 @@ angular.scenario.Describe.id = 0;
|
||||
/**
|
||||
* Defines a block to execute before each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
this.beforeEachFns.push(body);
|
||||
@@ -45,7 +49,7 @@ angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
/**
|
||||
* Defines a block to execute after each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.afterEach = function(body) {
|
||||
this.afterEachFns.push(body);
|
||||
@@ -54,8 +58,8 @@ angular.scenario.Describe.prototype.afterEach = function(body) {
|
||||
/**
|
||||
* Creates a new describe block that's a child of this one.
|
||||
*
|
||||
* @param {String} Name of the block. Appended to the parent block's name.
|
||||
* @param {Function} Body of the block.
|
||||
* @param {string} name Name of the block. Appended to the parent block's name.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
@@ -63,6 +67,19 @@ angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe() but makes ddescribe blocks the only to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.ddescribe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
child.only = true;
|
||||
this.children.push(child);
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a describe block.
|
||||
*/
|
||||
@@ -71,20 +88,31 @@ angular.scenario.Describe.prototype.xdescribe = angular.noop;
|
||||
/**
|
||||
* Defines a test.
|
||||
*
|
||||
* @param {String} Name of the test.
|
||||
* @param {Function} Body of the block.
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} vody Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.it = function(name, body) {
|
||||
var self = this;
|
||||
this.its.push({
|
||||
definition: this,
|
||||
only: this.only,
|
||||
name: name,
|
||||
before: self.setupBefore,
|
||||
before: this.setupBefore,
|
||||
body: body,
|
||||
after: self.setupAfter
|
||||
after: this.setupAfter
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it() but makes iit tests the only test to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.iit = function(name, body) {
|
||||
this.it.apply(this, arguments);
|
||||
this.its[this.its.length-1].only = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a test block.
|
||||
*/
|
||||
@@ -93,6 +121,15 @@ angular.scenario.Describe.prototype.xit = angular.noop;
|
||||
/**
|
||||
* Gets an array of functions representing all the tests (recursively).
|
||||
* that can be executed with SpecRunner's.
|
||||
*
|
||||
* @return {Array<Object>} Array of it blocks {
|
||||
* definition : Object // parent Describe
|
||||
* only: boolean
|
||||
* name: string
|
||||
* before: Function
|
||||
* body: Function
|
||||
* after: Function
|
||||
* }
|
||||
*/
|
||||
angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
var specs = arguments[0] || [];
|
||||
@@ -102,5 +139,11 @@ angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
angular.foreach(this.its, function(it) {
|
||||
specs.push(it);
|
||||
});
|
||||
return specs;
|
||||
var only = [];
|
||||
angular.foreach(specs, function(it) {
|
||||
if (it.only) {
|
||||
only.push(it);
|
||||
}
|
||||
});
|
||||
return (only.length && only) || specs;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* A future action in a spec.
|
||||
*
|
||||
* @param {String} name of the future action
|
||||
* @param {string} name of the future action
|
||||
* @param {Function} future callback(error, result)
|
||||
* @param {String} Optional. function that returns the file/line number.
|
||||
* @param {Function} Optional. function that returns the file/line number.
|
||||
*/
|
||||
angular.scenario.Future = function(name, behavior, line) {
|
||||
this.name = name;
|
||||
@@ -17,7 +17,7 @@ angular.scenario.Future = function(name, behavior, line) {
|
||||
/**
|
||||
* Executes the behavior of the closure.
|
||||
*
|
||||
* @param {Function} Callback function(error, result)
|
||||
* @param {Function} doneFn Callback function(error, result)
|
||||
*/
|
||||
angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
var self = this;
|
||||
@@ -37,6 +37,8 @@ angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
|
||||
/**
|
||||
* Configures the future to convert it's final with a function fn(value)
|
||||
*
|
||||
* @param {Function} fn function(value) that returns the parsed value
|
||||
*/
|
||||
angular.scenario.Future.prototype.parsedWith = function(fn) {
|
||||
this.parser = fn;
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* User Interface for the Scenario Runner.
|
||||
*
|
||||
* @param {Object} The jQuery UI object for the UI.
|
||||
*/
|
||||
angular.scenario.ui.Html = function(context) {
|
||||
this.context = context;
|
||||
context.append(
|
||||
'<div id="header">' +
|
||||
' <h1><span class="angular"><angular/></span>: Scenario Test Runner</h1>' +
|
||||
' <ul id="status-legend" class="status-display">' +
|
||||
' <li class="status-error">0 Errors</li>' +
|
||||
' <li class="status-failure">0 Failures</li>' +
|
||||
' <li class="status-success">0 Passed</li>' +
|
||||
' </ul>' +
|
||||
'</div>' +
|
||||
'<div id="specs">' +
|
||||
' <div class="test-children"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The severity order of an error.
|
||||
*/
|
||||
angular.scenario.ui.Html.SEVERITY = ['pending', 'success', 'failure', 'error'];
|
||||
|
||||
/**
|
||||
* Adds a new spec to the UI.
|
||||
*
|
||||
* @param {Object} The spec object created by the Describe object.
|
||||
*/
|
||||
angular.scenario.ui.Html.prototype.addSpec = function(spec) {
|
||||
var self = this;
|
||||
var specContext = this.findContext(spec.definition);
|
||||
specContext.find('> .tests').append(
|
||||
'<li class="status-pending test-it"></li>'
|
||||
);
|
||||
specContext = specContext.find('> .tests li:last');
|
||||
return new angular.scenario.ui.Html.Spec(specContext, spec.name,
|
||||
function(status) {
|
||||
status = self.context.find('#status-legend .status-' + status);
|
||||
var parts = status.text().split(' ');
|
||||
var value = (parts[0] * 1) + 1;
|
||||
status.text(value + ' ' + parts[1]);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the context of a spec block defined by the passed definition.
|
||||
*
|
||||
* @param {Object} The definition created by the Describe object.
|
||||
*/
|
||||
angular.scenario.ui.Html.prototype.findContext = function(definition) {
|
||||
var self = this;
|
||||
var path = [];
|
||||
var currentContext = this.context.find('#specs');
|
||||
var currentDefinition = definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
angular.foreach(path, function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!self.context.find('#' + id).length) {
|
||||
currentContext.find('> .test-children').append(
|
||||
'<div class="test-describe" id="' + id + '">' +
|
||||
' <h2></h2>' +
|
||||
' <div class="test-children"></div>' +
|
||||
' <ul class="tests"></ul>' +
|
||||
'</div>'
|
||||
);
|
||||
self.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
|
||||
}
|
||||
currentContext = self.context.find('#' + id);
|
||||
});
|
||||
return this.context.find('#describe-' + definition.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* A spec block in the UI.
|
||||
*
|
||||
* @param {Object} The jQuery object for the context of the spec.
|
||||
* @param {String} The name of the spec.
|
||||
* @param {Function} Callback function(status) to call when complete.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
|
||||
this.status = 'pending';
|
||||
this.context = context;
|
||||
this.startTime = new Date().getTime();
|
||||
this.doneFn = doneFn;
|
||||
context.append(
|
||||
'<div class="test-info">' +
|
||||
' <p class="test-title">' +
|
||||
' <span class="timer-result"></span>' +
|
||||
' <span class="test-name"></span>' +
|
||||
' </p>' +
|
||||
'</div>' +
|
||||
'<div class="scrollpane">' +
|
||||
' <ol class="test-actions">' +
|
||||
' </ol>' +
|
||||
'</div>'
|
||||
);
|
||||
context.find('> .test-info').click(function() {
|
||||
var scrollpane = context.find('> .scrollpane');
|
||||
var actions = scrollpane.find('> .test-actions');
|
||||
var name = context.find('> .test-info .test-name');
|
||||
if (actions.find(':visible').length) {
|
||||
actions.hide();
|
||||
name.removeClass('open').addClass('closed');
|
||||
} else {
|
||||
actions.show();
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
name.removeClass('closed').addClass('open');
|
||||
}
|
||||
});
|
||||
context.find('> .test-info .test-name').text('it ' + name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new Step to this spec and returns it.
|
||||
*
|
||||
* @param {String} The name of the step.
|
||||
* @param {Function} function() that returns a string with the file/line number
|
||||
* where the step was added from.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.addStep = function(name, location) {
|
||||
this.context.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
|
||||
var stepContext = this.context.find('> .scrollpane .test-actions li:last');
|
||||
var self = this;
|
||||
return new angular.scenario.ui.Html.Step(stepContext, name, location, function(status) {
|
||||
if (indexOf(angular.scenario.ui.Html.SEVERITY, status) >
|
||||
indexOf(angular.scenario.ui.Html.SEVERITY, self.status)) {
|
||||
self.status = status;
|
||||
}
|
||||
var scrollpane = self.context.find('> .scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Completes the spec and sets the timer value.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.complete = function() {
|
||||
this.context.removeClass('status-pending');
|
||||
var endTime = new Date().getTime();
|
||||
this.context.find("> .test-info .timer-result").
|
||||
text((endTime - this.startTime) + "ms");
|
||||
if (this.status === 'success') {
|
||||
this.context.find('> .test-info .test-name').addClass('closed');
|
||||
this.context.find('> .scrollpane .test-actions').hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the spec, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.finish = function() {
|
||||
this.complete();
|
||||
this.context.addClass('status-' + this.status);
|
||||
this.doneFn(this.status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the spec, but with a Fatal Error.
|
||||
*
|
||||
* @param {Object} Required error
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.error = function(error) {
|
||||
this.status = 'error';
|
||||
this.context.append('<pre></pre>');
|
||||
this.context.find('> pre').text(formatException(error));
|
||||
this.finish();
|
||||
};
|
||||
|
||||
/**
|
||||
* A single step inside an it block (or a before/after function).
|
||||
*
|
||||
* @param {Object} The jQuery object for the context of the step.
|
||||
* @param {String} The name of the step.
|
||||
* @param {Function} function() that returns file/line number of step.
|
||||
* @param {Function} Callback function(status) to call when complete.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step = function(context, name, location, doneFn) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
this.startTime = new Date().getTime();
|
||||
this.doneFn = doneFn;
|
||||
context.append(
|
||||
'<div class="timer-result"></div>' +
|
||||
'<div class="test-title"></div>'
|
||||
);
|
||||
context.find('> .test-title').text(name);
|
||||
var scrollpane = context.parents('.scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Completes the step and sets the timer value.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.complete = function(error) {
|
||||
this.context.removeClass('status-pending');
|
||||
var endTime = new Date().getTime();
|
||||
this.context.find(".timer-result").
|
||||
text((endTime - this.startTime) + "ms");
|
||||
if (error) {
|
||||
if (!this.context.find('.test-title pre').length) {
|
||||
this.context.find('.test-title').append('<pre></pre>');
|
||||
}
|
||||
var message = _jQuery.trim(this.location() + '\n\n' + formatException(error));
|
||||
this.context.find('.test-title pre').text(message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the step, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
|
||||
this.complete(error);
|
||||
if (error) {
|
||||
this.context.addClass('status-failure');
|
||||
this.doneFn('failure');
|
||||
} else {
|
||||
this.context.addClass('status-success');
|
||||
this.doneFn('success');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the step, but with a Fatal Error.
|
||||
*
|
||||
* @param {Object} Required error
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.error = function(error) {
|
||||
this.complete(error);
|
||||
this.context.addClass('status-error');
|
||||
this.doneFn('error');
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Maintains an object tree from the runner events.
|
||||
*
|
||||
* @param {Object} runner The scenario Runner instance to connect to.
|
||||
*
|
||||
* TODO(esprehn): Every output type creates one of these, but we probably
|
||||
* want one glonal shared instance. Need to handle events better too
|
||||
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
|
||||
* silliness.
|
||||
*/
|
||||
angular.scenario.ObjectModel = function(runner) {
|
||||
var self = this;
|
||||
|
||||
this.specMap = {};
|
||||
this.value = {
|
||||
name: '',
|
||||
children: {}
|
||||
};
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var block = self.value;
|
||||
angular.foreach(self.getDefinitionPath(spec), function(def) {
|
||||
if (!block.children[def.name]) {
|
||||
block.children[def.name] = {
|
||||
id: def.id,
|
||||
name: def.name,
|
||||
children: {},
|
||||
specs: {}
|
||||
};
|
||||
}
|
||||
block = block.children[def.name];
|
||||
});
|
||||
self.specMap[spec.id] = block.specs[spec.name] =
|
||||
new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
|
||||
});
|
||||
|
||||
runner.on('SpecError', function(spec, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
it.status = 'error';
|
||||
it.error = error;
|
||||
});
|
||||
|
||||
runner.on('SpecEnd', function(spec) {
|
||||
var it = self.getSpec(spec.id);
|
||||
complete(it);
|
||||
});
|
||||
|
||||
runner.on('StepBegin', function(spec, step) {
|
||||
var it = self.getSpec(spec.id);
|
||||
it.steps.push(new angular.scenario.ObjectModel.Step(step.name));
|
||||
});
|
||||
|
||||
runner.on('StepEnd', function(spec, step) {
|
||||
var it = self.getSpec(spec.id);
|
||||
if (it.getLastStep().name !== step.name)
|
||||
throw 'Events fired in the wrong order. Step names don\' match.';
|
||||
complete(it.getLastStep());
|
||||
});
|
||||
|
||||
runner.on('StepFailure', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
var item = it.getLastStep();
|
||||
item.error = error;
|
||||
if (!it.status) {
|
||||
it.status = item.status = 'failure';
|
||||
}
|
||||
});
|
||||
|
||||
runner.on('StepError', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
var item = it.getLastStep();
|
||||
it.status = 'error';
|
||||
item.status = 'error';
|
||||
item.error = error;
|
||||
});
|
||||
|
||||
function complete(item) {
|
||||
item.endTime = new Date().getTime();
|
||||
item.duration = item.endTime - item.startTime;
|
||||
item.status = item.status || 'success';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the path of definition describe blocks that wrap around
|
||||
* this spec.
|
||||
*
|
||||
* @param spec Spec to compute the path for.
|
||||
* @return {Array<Describe>} The describe block path
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getDefinitionPath = function(spec) {
|
||||
var path = [];
|
||||
var currentDefinition = spec.definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a spec by id.
|
||||
*
|
||||
* @param {string} The id of the spec to get the object for.
|
||||
* @return {Object} the Spec instance
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getSpec = function(id) {
|
||||
return this.specMap[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single it block.
|
||||
*
|
||||
* @param {string} id Id of the spec
|
||||
* @param {string} name Name of the spec
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec = function(id, name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
this.steps = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new step to the Spec.
|
||||
*
|
||||
* @param {string} step Name of the step (really name of the future)
|
||||
* @return {Object} the added step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.addStep = function(name) {
|
||||
var step = new angular.scenario.ObjectModel.Step(name);
|
||||
this.steps.push(step);
|
||||
return step;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the most recent step.
|
||||
*
|
||||
* @return {Object} the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
|
||||
return this.steps[this.steps.length-1];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single step inside a Spec.
|
||||
*
|
||||
* @param {string} step Name of the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Step = function(name) {
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
};
|
||||
+107
-21
@@ -2,13 +2,16 @@
|
||||
* Runner for scenarios.
|
||||
*/
|
||||
angular.scenario.Runner = function($window) {
|
||||
this.listeners = [];
|
||||
this.$window = $window;
|
||||
this.rootDescribe = new angular.scenario.Describe();
|
||||
this.currentDescribe = this.rootDescribe;
|
||||
this.api = {
|
||||
it: this.it,
|
||||
iit: this.iit,
|
||||
xit: angular.noop,
|
||||
describe: this.describe,
|
||||
ddescribe: this.ddescribe,
|
||||
xdescribe: angular.noop,
|
||||
beforeEach: this.beforeEach,
|
||||
afterEach: this.afterEach
|
||||
@@ -18,11 +21,42 @@ angular.scenario.Runner = function($window) {
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event which notifies listeners and passes extra
|
||||
* arguments.
|
||||
*
|
||||
* @param {string} eventName Name of the event to fire.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.emit = function(eventName) {
|
||||
var self = this;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
eventName = eventName.toLowerCase();
|
||||
if (!this.listeners[eventName])
|
||||
return;
|
||||
angular.foreach(this.listeners[eventName], function(listener) {
|
||||
listener.apply(self, args);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener for an event.
|
||||
*
|
||||
* @param {string} eventName The name of the event to add a handler for
|
||||
* @param {string} listener The fn(...) that takes the extra arguments from emit()
|
||||
*/
|
||||
angular.scenario.Runner.prototype.on = function(eventName, listener) {
|
||||
eventName = eventName.toLowerCase();
|
||||
this.listeners[eventName] = this.listeners[eventName] || [];
|
||||
this.listeners[eventName].push(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
var self = this;
|
||||
@@ -37,20 +71,57 @@ angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe, but makes ddescribe the only blocks to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.ddescribe = function(name, body) {
|
||||
var self = this;
|
||||
this.currentDescribe.ddescribe(name, function() {
|
||||
var parentDescribe = self.currentDescribe;
|
||||
self.currentDescribe = this;
|
||||
try {
|
||||
body.call(this);
|
||||
} finally {
|
||||
self.currentDescribe = parentDescribe;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a test in a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.it = function(name, body) {
|
||||
this.currentDescribe.it(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it, but makes iit tests the only tests to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.iit = function(name, body) {
|
||||
this.currentDescribe.iit(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
@@ -61,6 +132,8 @@ angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
* Defines a function to be called after each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
@@ -68,24 +141,29 @@ angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
* Creates a new spec runner.
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
* @private
|
||||
* @param {Object} scope parent scope
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClass, specsDone) {
|
||||
var $root = angular.scope({}, angular.service);
|
||||
angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) {
|
||||
return scope.$new(angular.scenario.SpecRunner);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs all the loaded tests with the specified runner class on the
|
||||
* provided application.
|
||||
*
|
||||
* @param {angular.scenario.Application} application App to remote control.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(application) {
|
||||
var self = this;
|
||||
var specs = this.rootDescribe.getSpecs();
|
||||
var $root = angular.scope(this);
|
||||
$root.application = application;
|
||||
$root.ui = ui;
|
||||
$root.setTimeout = function() {
|
||||
return self.$window.setTimeout.apply(self.$window, arguments);
|
||||
};
|
||||
asyncForEach(specs, function(spec, specDone) {
|
||||
this.emit('RunnerBegin');
|
||||
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
|
||||
var dslCache = {};
|
||||
var runner = angular.scope($root);
|
||||
runner.$become(specRunnerClass);
|
||||
var runner = self.createSpecRunner_($root);
|
||||
angular.foreach(angular.scenario.dsl, function(fn, key) {
|
||||
dslCache[key] = fn.call($root);
|
||||
});
|
||||
@@ -105,16 +183,24 @@ angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClas
|
||||
// Make these methods work on the current chain
|
||||
scope.addFuture = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return specRunnerClass.prototype.addFuture.apply(scope, arguments);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFuture.apply(scope, arguments);
|
||||
};
|
||||
scope.addFutureAction = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return specRunnerClass.prototype.addFutureAction.apply(scope, arguments);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFutureAction.apply(scope, arguments);
|
||||
};
|
||||
|
||||
return scope.dsl[key].apply(scope, arguments);
|
||||
};
|
||||
});
|
||||
runner.run(ui, spec, specDone);
|
||||
}, specsDone || angular.noop);
|
||||
runner.run(spec, specDone);
|
||||
},
|
||||
function(error) {
|
||||
if (error) {
|
||||
self.emit('RunnerError', error);
|
||||
}
|
||||
self.emit('RunnerEnd');
|
||||
});
|
||||
};
|
||||
|
||||
+109
-25
@@ -1,3 +1,4 @@
|
||||
|
||||
/**
|
||||
* Setup file for the Scenario.
|
||||
* Must be first in the compilation/bootstrap list.
|
||||
@@ -6,8 +7,15 @@
|
||||
// Public namespace
|
||||
angular.scenario = angular.scenario || {};
|
||||
|
||||
// Namespace for the UI
|
||||
angular.scenario.ui = angular.scenario.ui || {};
|
||||
/**
|
||||
* Defines a new output format.
|
||||
*
|
||||
* @param {string} name the name of the new output format
|
||||
* @param {Function} fn function(context, runner) that generates the output
|
||||
*/
|
||||
angular.scenario.output = angular.scenario.output || function(name, fn) {
|
||||
angular.scenario.output[name] = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a new DSL statement. If your factory function returns a Future
|
||||
@@ -18,8 +26,8 @@ angular.scenario.ui = angular.scenario.ui || {};
|
||||
* set on "this" in your statement function are available in the chained
|
||||
* functions.
|
||||
*
|
||||
* @param {String} The name of the statement
|
||||
* @param {Function} Factory function(application), return a function for
|
||||
* @param {string} name The name of the statement
|
||||
* @param {Function} fn Factory function(), return a function for
|
||||
* the statement.
|
||||
*/
|
||||
angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
@@ -54,8 +62,8 @@ angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
* against. Your function should return a boolean. The future is automatically
|
||||
* created for you.
|
||||
*
|
||||
* @param {String} The name of the matcher
|
||||
* @param {Function} The matching function(expected).
|
||||
* @param {string} name The name of the matcher
|
||||
* @param {Function} fn The matching function(expected).
|
||||
*/
|
||||
angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
angular.scenario.matcher[name] = function(expected) {
|
||||
@@ -64,7 +72,7 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
prefix += 'not ';
|
||||
}
|
||||
var self = this;
|
||||
this.addFuture(prefix + name + ' ' + angular.toJson(expected),
|
||||
this.addFuture(prefix + name + ' ' + angular.toJson(expected),
|
||||
function(done) {
|
||||
var error;
|
||||
self.actual = self.future.value;
|
||||
@@ -78,14 +86,66 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization function for the scenario runner.
|
||||
*
|
||||
* @param {angular.scenario.Runner} $scenario The runner to setup
|
||||
* @param {Object} config Config options
|
||||
*/
|
||||
function angularScenarioInit($scenario, config) {
|
||||
var href = window.location.href;
|
||||
var body = _jQuery(document.body);
|
||||
var output = [];
|
||||
|
||||
if (config.scenario_output) {
|
||||
output = config.scenario_output.split(',');
|
||||
}
|
||||
|
||||
angular.foreach(angular.scenario.output, function(fn, name) {
|
||||
if (!output.length || indexOf(output,name) != -1) {
|
||||
var context = body.append('<div></div>').find('div:last');
|
||||
context.attr('id', name);
|
||||
fn.call({}, context, $scenario);
|
||||
}
|
||||
});
|
||||
|
||||
if (!/^http/.test(href) && !/^https/.test(href)) {
|
||||
body.append('<p id="system-error"></p>');
|
||||
body.find('#system-error').text(
|
||||
'Scenario runner must be run using http or https. The protocol ' +
|
||||
href.split(':')[0] + ':// is not supported.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var appFrame = body.append('<div id="application"></div>').find('#application');
|
||||
var application = new angular.scenario.Application(appFrame);
|
||||
|
||||
$scenario.on('RunnerEnd', function() {
|
||||
appFrame.css('display', 'none');
|
||||
appFrame.find('iframe').attr('src', 'about:blank');
|
||||
});
|
||||
|
||||
$scenario.on('RunnerError', function(error) {
|
||||
if (window.console) {
|
||||
console.log(formatException(error));
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
});
|
||||
|
||||
$scenario.run(application);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through list with iterator function that must call the
|
||||
* continueFunction to continute iterating.
|
||||
*
|
||||
* @param {Array} list to iterate over
|
||||
* @param {Function} Callback function(value, continueFunction)
|
||||
* @param {Function} Callback function(error, result) called when iteration
|
||||
* finishes or an error occurs.
|
||||
* @param {Array} list list to iterate over
|
||||
* @param {Function} iterator Callback function(value, continueFunction)
|
||||
* @param {Function} done Callback function(error, result) called when
|
||||
* iteration finishes or an error occurs.
|
||||
*/
|
||||
function asyncForEach(list, iterator, done) {
|
||||
var i = 0;
|
||||
@@ -110,8 +170,8 @@ function asyncForEach(list, iterator, done) {
|
||||
* Formats an exception into a string with the stack trace, but limits
|
||||
* to a specific line length.
|
||||
*
|
||||
* @param {Object} the exception to format, can be anything throwable
|
||||
* @param {Number} Optional. max lines of the stack trace to include
|
||||
* @param {Object} error The exception to format, can be anything throwable
|
||||
* @param {Number} maxStackLines Optional. max lines of the stack trace to include
|
||||
* default is 5.
|
||||
*/
|
||||
function formatException(error, maxStackLines) {
|
||||
@@ -129,18 +189,20 @@ function formatException(error, maxStackLines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that gets the file name and line number from a
|
||||
* Returns a function that gets the file name and line number from a
|
||||
* location in the stack if available based on the call site.
|
||||
*
|
||||
* Note: this returns another function because accessing .stack is very
|
||||
* expensive in Chrome.
|
||||
*
|
||||
* @param {Number} offset Number of stack lines to skip
|
||||
*/
|
||||
function callerFile(offset) {
|
||||
var error = new Error();
|
||||
|
||||
|
||||
return function() {
|
||||
var line = (error.stack || '').split('\n')[offset];
|
||||
|
||||
|
||||
// Clean up the stack trace line
|
||||
if (line) {
|
||||
if (line.indexOf('@') !== -1) {
|
||||
@@ -151,7 +213,7 @@ function callerFile(offset) {
|
||||
line = line.substring(line.indexOf('(')+1).replace(')', '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return line || '';
|
||||
};
|
||||
}
|
||||
@@ -161,7 +223,7 @@ function callerFile(offset) {
|
||||
* not specified.
|
||||
*
|
||||
* @param {Object} Either a wrapped jQuery/jqLite node or a DOMElement
|
||||
* @param {String} Optional event type.
|
||||
* @param {string} Optional event type.
|
||||
*/
|
||||
function browserTrigger(element, type) {
|
||||
if (element && !element.nodeName) element = element[0];
|
||||
@@ -188,7 +250,22 @@ function browserTrigger(element, type) {
|
||||
type = 'change';
|
||||
}
|
||||
if (msie) {
|
||||
switch(element.type) {
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
element.checked = !element.checked;
|
||||
break;
|
||||
}
|
||||
element.fireEvent('on' + type);
|
||||
if (lowercase(element.type) == 'submit') {
|
||||
while(element) {
|
||||
if (lowercase(element.nodeName) == 'form') {
|
||||
element.fireEvent('onsubmit');
|
||||
break;
|
||||
}
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var evnt = document.createEvent('MouseEvents');
|
||||
evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
|
||||
@@ -200,13 +277,20 @@ function browserTrigger(element, type) {
|
||||
* Don't use the jQuery trigger method since it works incorrectly.
|
||||
*
|
||||
* jQuery notifies listeners and then changes the state of a checkbox and
|
||||
* does not create a real browser event. A real click changes the state of
|
||||
* does not create a real browser event. A real click changes the state of
|
||||
* the checkbox and then notifies listeners.
|
||||
*
|
||||
*
|
||||
* To work around this we instead use our own handler that fires a real event.
|
||||
*/
|
||||
_jQuery.fn.trigger = function(type) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
});
|
||||
};
|
||||
(function(fn){
|
||||
var parentTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keyup)/.test(type)) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
});
|
||||
}
|
||||
return parentTrigger.apply(this, arguments);
|
||||
};
|
||||
})(_jQuery.fn);
|
||||
|
||||
|
||||
+31
-25
@@ -15,14 +15,15 @@ angular.scenario.SpecRunner = function() {
|
||||
* Executes a spec which is an it block with associated before/after functions
|
||||
* based on the describe nesting.
|
||||
*
|
||||
* @param {Object} An angular.scenario.UI implementation
|
||||
* @param {Object} A spec object
|
||||
* @param {Object} An angular.scenario.Application instance
|
||||
* @param {Object} spec A spec object
|
||||
* @param {Object} specDone An angular.scenario.Application instance
|
||||
* @param {Function} Callback function that is called when the spec finshes.
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
angular.scenario.SpecRunner.prototype.run = function(spec, specDone) {
|
||||
var self = this;
|
||||
var specUI = ui.addSpec(spec);
|
||||
this.spec = spec;
|
||||
|
||||
this.emit('SpecBegin', spec);
|
||||
|
||||
try {
|
||||
spec.before.call(this);
|
||||
@@ -30,7 +31,8 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
this.afterIndex = this.futures.length;
|
||||
spec.after.call(this);
|
||||
} catch (e) {
|
||||
specUI.error(e);
|
||||
this.emit('SpecError', spec, e);
|
||||
this.emit('SpecEnd', spec);
|
||||
specDone();
|
||||
return;
|
||||
}
|
||||
@@ -42,32 +44,36 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
self.error = true;
|
||||
done(null, self.afterIndex);
|
||||
};
|
||||
|
||||
var spec = this;
|
||||
|
||||
asyncForEach(
|
||||
this.futures,
|
||||
function(future, futureDone) {
|
||||
var stepUI = specUI.addStep(future.name, future.line);
|
||||
self.step = future;
|
||||
self.emit('StepBegin', spec, future);
|
||||
try {
|
||||
future.execute(function(error) {
|
||||
stepUI.finish(error);
|
||||
if (error) {
|
||||
self.emit('StepFailure', spec, future, error);
|
||||
self.emit('StepEnd', spec, future);
|
||||
return handleError(error, futureDone);
|
||||
}
|
||||
spec.$window.setTimeout( function() { futureDone(); }, 0);
|
||||
self.emit('StepEnd', spec, future);
|
||||
self.$window.setTimeout(function() { futureDone(); }, 0);
|
||||
});
|
||||
} catch (e) {
|
||||
stepUI.error(e);
|
||||
self.emit('StepError', spec, future, e);
|
||||
self.emit('StepEnd', spec, future);
|
||||
handleError(e, futureDone);
|
||||
}
|
||||
},
|
||||
function(e) {
|
||||
if (e) {
|
||||
specUI.error(e);
|
||||
} else {
|
||||
specUI.finish();
|
||||
self.emit('SpecError', spec, e);
|
||||
}
|
||||
specDone();
|
||||
self.emit('SpecEnd', spec);
|
||||
// Call done in a timeout so exceptions don't recursively
|
||||
// call this function
|
||||
self.$window.setTimeout(function() { specDone(); }, 0);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -77,9 +83,9 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
* @param {Function} fn() that returns file/line number
|
||||
* @param {string} name Name of the future
|
||||
* @param {Function} behavior Behavior of the future
|
||||
* @param {Function} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior, line) {
|
||||
var future = new angular.scenario.Future(name, angular.bind(this, behavior), line);
|
||||
@@ -92,19 +98,19 @@ angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior, line)
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
* @param {Function} fn() that returns file/line number
|
||||
* @param {string} name Name of the future
|
||||
* @param {Function} behavior Behavior of the future
|
||||
* @param {Function} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior, line) {
|
||||
var self = this;
|
||||
return this.addFuture(name, function(done) {
|
||||
this.application.executeAction(function($window, $document) {
|
||||
|
||||
//TODO(esprehn): Refactor this so it doesn't need to be in here.
|
||||
$document.elements = function(selector) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
if (self.selector) {
|
||||
selector = self.selector + ' ' + (selector || '');
|
||||
}
|
||||
selector = (self.selector || '') + ' ' + (selector || '');
|
||||
angular.foreach(args, function(value, index) {
|
||||
selector = selector.replace('$' + (index + 1), value);
|
||||
});
|
||||
|
||||
+12
-26
@@ -1,6 +1,6 @@
|
||||
(function(previousOnLoad){
|
||||
var prefix = (function(){
|
||||
var filename = /(.*\/)bootstrap.js(#(.*))?/;
|
||||
var filename = /(.*\/)angular-bootstrap.js(#(.*))?/;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
@@ -23,50 +23,36 @@
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
_jQuery(document.body).append(
|
||||
'<div id="runner"></div>' +
|
||||
'<div id="frame"></div>'
|
||||
);
|
||||
var frame = _jQuery('#frame');
|
||||
var runner = _jQuery('#runner');
|
||||
var application = new angular.scenario.Application(frame);
|
||||
var ui = new angular.scenario.ui.Html(runner);
|
||||
$scenario.run(ui, application, angular.scenario.SpecRunner, function(error) {
|
||||
frame.remove();
|
||||
if (error) {
|
||||
if (window.console) {
|
||||
console.log(error.stack || error);
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
};
|
||||
|
||||
addCSS("../../css/angular-scenario.css");
|
||||
addScript("../../lib/jquery/jquery-1.4.2.js");
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
'</script>'
|
||||
);
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
'</script>'
|
||||
);
|
||||
addScript("../angular-bootstrap.js");
|
||||
|
||||
addScript("Scenario.js");
|
||||
addScript("Application.js");
|
||||
addScript("Describe.js");
|
||||
addScript("Future.js");
|
||||
addScript("HtmlUI.js");
|
||||
addScript("Runner.js");
|
||||
addScript("SpecRunner.js");
|
||||
addScript("dsl.js");
|
||||
addScript("matchers.js");
|
||||
addScript("ObjectModel.js");
|
||||
addScript("output/Html.js");
|
||||
addScript("output/Json.js");
|
||||
addScript("output/Object.js");
|
||||
addScript("output/Xml.js");
|
||||
|
||||
// Create the runner (which also sets up the global API)
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var $scenario = new angular.scenario.Runner(window);' +
|
||||
'var $scenario = new angular.scenario.Runner(window, angular.scenario.SpecRunner);' +
|
||||
'</script>'
|
||||
);
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(window, document, previousOnLoad){
|
||||
var _jQuery = window.jQuery.noConflict(true);
|
||||
var _jQuery = window.jQuery.noConflict(true);
|
||||
|
||||
@@ -4,25 +4,7 @@
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
_jQuery(document.body).append(
|
||||
'<div id="runner"></div>' +
|
||||
'<div id="frame"></div>'
|
||||
);
|
||||
var frame = _jQuery('#frame');
|
||||
var runner = _jQuery('#runner');
|
||||
var application = new angular.scenario.Application(frame);
|
||||
var ui = new angular.scenario.ui.Html(runner);
|
||||
$scenario.run(ui, application, angular.scenario.SpecRunner, function(error) {
|
||||
frame.remove();
|
||||
if (error) {
|
||||
if (window.console) {
|
||||
console.log(error.stack || error);
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
};
|
||||
|
||||
})(window, document, window.onload);
|
||||
|
||||
+216
-92
@@ -1,29 +1,113 @@
|
||||
/**
|
||||
* Shared DSL statements that are useful to all scenarios.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* wait() waits until you call resume() in the console
|
||||
*/
|
||||
angular.scenario.dsl('wait', function() {
|
||||
angular.scenario.dsl('wait', function() {
|
||||
return function() {
|
||||
return this.addFuture('waiting for you to call resume() in the console', function(done) {
|
||||
return this.addFuture('waiting for you to resume', function(done) {
|
||||
this.emit('InteractiveWait', this.spec, this.step);
|
||||
this.$window.resume = function() { done(); };
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* pause(seconds) pauses the test for specified number of seconds
|
||||
*/
|
||||
* Usage:
|
||||
* pause(seconds) pauses the test for specified number of seconds
|
||||
*/
|
||||
angular.scenario.dsl('pause', function() {
|
||||
return function(time) {
|
||||
return this.addFuture('pause for ' + time + ' seconds', function(done) {
|
||||
this.setTimeout(function() { done(null, time * 1000); }, time * 1000);
|
||||
});
|
||||
};
|
||||
return function(time) {
|
||||
return this.addFuture('pause for ' + time + ' seconds', function(done) {
|
||||
this.$window.setTimeout(function() { done(null, time * 1000); }, time * 1000);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* browser().navigateTo(url) Loads the url into the frame
|
||||
* browser().navigateTo(url, fn) where fn(url) is called and returns the URL to navigate to
|
||||
* browser().reload() refresh the page (reload the same URL)
|
||||
* browser().location().href() the full URL of the page
|
||||
* browser().location().hash() the full hash in the url
|
||||
* browser().location().path() the full path in the url
|
||||
* browser().location().hashSearch() the hashSearch Object from angular
|
||||
* browser().location().hashPath() the hashPath string from angular
|
||||
*/
|
||||
angular.scenario.dsl('browser', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.navigateTo = function(url, delegate) {
|
||||
var application = this.application;
|
||||
return this.addFuture("browser navigate to '" + url + "'", function(done) {
|
||||
if (delegate) {
|
||||
url = delegate.call(this, url);
|
||||
}
|
||||
application.navigateTo(url, function() {
|
||||
done(null, url);
|
||||
}, done);
|
||||
});
|
||||
};
|
||||
|
||||
chain.reload = function() {
|
||||
var application = this.application;
|
||||
return this.addFutureAction('browser reload', function($window, $document, done) {
|
||||
var href = $window.location.href;
|
||||
application.navigateTo(href, function() {
|
||||
done(null, href);
|
||||
}, done);
|
||||
});
|
||||
};
|
||||
|
||||
chain.location = function() {
|
||||
var api = {};
|
||||
|
||||
api.href = function() {
|
||||
return this.addFutureAction('browser url', function($window, $document, done) {
|
||||
done(null, $window.location.href);
|
||||
});
|
||||
};
|
||||
|
||||
api.hash = function() {
|
||||
return this.addFutureAction('browser url hash', function($window, $document, done) {
|
||||
done(null, $window.location.hash.replace('#', ''));
|
||||
});
|
||||
};
|
||||
|
||||
api.path = function() {
|
||||
return this.addFutureAction('browser url path', function($window, $document, done) {
|
||||
done(null, $window.location.pathname);
|
||||
});
|
||||
};
|
||||
|
||||
api.search = function() {
|
||||
return this.addFutureAction('browser url search', function($window, $document, done) {
|
||||
done(null, $window.angular.scope().$location.search);
|
||||
});
|
||||
};
|
||||
|
||||
api.hashSearch = function() {
|
||||
return this.addFutureAction('browser url hash search', function($window, $document, done) {
|
||||
done(null, $window.angular.scope().$location.hashSearch);
|
||||
});
|
||||
};
|
||||
|
||||
api.hashPath = function() {
|
||||
return this.addFutureAction('browser url hash path', function($window, $document, done) {
|
||||
done(null, $window.angular.scope().$location.hashPath);
|
||||
});
|
||||
};
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
return function(time) {
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -49,43 +133,19 @@ angular.scenario.dsl('expect', function() {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* navigateTo(future|string) where url a string or future with a value
|
||||
* of a URL to navigate to
|
||||
*/
|
||||
angular.scenario.dsl('navigateTo', function() {
|
||||
return function(url, delegate) {
|
||||
var application = this.application;
|
||||
return this.addFuture('navigate to ' + url, function(done) {
|
||||
if (delegate) {
|
||||
url = delegate.call(this, url);
|
||||
}
|
||||
application.navigateTo(url, function() {
|
||||
application.executeAction(function($window) {
|
||||
if ($window.angular) {
|
||||
var $browser = $window.angular.service.$browser();
|
||||
$browser.poll();
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
done(null, url);
|
||||
});
|
||||
} else {
|
||||
done(null, url);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* using(selector) scopes the next DSL element selection
|
||||
* using(selector, label) scopes the next DSL element selection
|
||||
*
|
||||
* ex.
|
||||
* using('#foo').input('bar')
|
||||
* using('#foo', "'Foo' text field").input('bar')
|
||||
*/
|
||||
angular.scenario.dsl('using', function() {
|
||||
return function(selector) {
|
||||
this.selector = (this.selector||'') + ' ' + selector;
|
||||
return function(selector, label) {
|
||||
this.selector = _jQuery.trim((this.selector||'') + ' ' + selector);
|
||||
if (angular.isString(label) && label.length) {
|
||||
this.label = label + ' ( ' + this.selector + ' )';
|
||||
} else {
|
||||
this.label = this.selector;
|
||||
}
|
||||
return this.dsl;
|
||||
};
|
||||
});
|
||||
@@ -95,17 +155,27 @@ angular.scenario.dsl('using', function() {
|
||||
* binding(name) returns the value of a 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 element;
|
||||
try {
|
||||
element = $document.elements('[ng\\:bind-template*="{{$1}}"]', name);
|
||||
} catch(e) {
|
||||
if (e.type !== 'selector')
|
||||
throw e;
|
||||
element = $document.elements('[ng\\:bind="$1"]', name);
|
||||
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;
|
||||
}
|
||||
}
|
||||
done(null, element.text());
|
||||
done("Binding selector '" + name + "' did not match.");
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -151,23 +221,51 @@ angular.scenario.dsl('input', function() {
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* repeater('#products table').count() number of rows
|
||||
* repeater('#products table').row(1) all bindings in row as an array
|
||||
* repeater('#products table').column('product.name') all values across all rows in an array
|
||||
* 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
|
||||
* repeater('#products table', 'Product List').row(1) all bindings in row as an array
|
||||
* repeater('#products table', 'Product List').column('product.name') all values across all rows in an array
|
||||
*/
|
||||
angular.scenario.dsl('repeater', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.count = function() {
|
||||
return this.addFutureAction('repeater ' + this.selector + ' count', function($window, $document, done) {
|
||||
done(null, $document.elements().size());
|
||||
return this.addFutureAction("repeater '" + this.label + "' count", function($window, $document, done) {
|
||||
try {
|
||||
done(null, $document.elements().length);
|
||||
} catch (e) {
|
||||
done(null, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
chain.column = function(binding) {
|
||||
return this.addFutureAction('repeater ' + this.selector + ' column ' + binding, function($window, $document, done) {
|
||||
return this.addFutureAction("repeater '" + this.label + "' column '" + binding + "'", function($window, $document, done) {
|
||||
var values = [];
|
||||
$document.elements().each(function() {
|
||||
_jQuery(this).find(':visible').each(function() {
|
||||
@@ -182,14 +280,14 @@ angular.scenario.dsl('repeater', function() {
|
||||
};
|
||||
|
||||
chain.row = function(index) {
|
||||
return this.addFutureAction('repeater ' + this.selector + ' row ' + index, function($window, $document, done) {
|
||||
return this.addFutureAction("repeater '" + this.label + "' row '" + index + "'", function($window, $document, done) {
|
||||
var values = [];
|
||||
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 (element.attr('ng:bind')) {
|
||||
if (angular.isDefined(element.attr('ng:bind'))) {
|
||||
values.push(element.text());
|
||||
}
|
||||
});
|
||||
@@ -197,22 +295,22 @@ angular.scenario.dsl('repeater', function() {
|
||||
});
|
||||
};
|
||||
|
||||
return function(selector) {
|
||||
this.dsl.using(selector);
|
||||
return function(selector, label) {
|
||||
this.dsl.using(selector, label);
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* select(selector).option('value') select one option
|
||||
* select(selector).options('value1', 'value2', ...) select options from a multi select
|
||||
* select(name).option('value') select one option
|
||||
* select(name).options('value1', 'value2', ...) select options from a multi select
|
||||
*/
|
||||
angular.scenario.dsl('select', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.option = function(value) {
|
||||
return this.addFutureAction('select ' + this.name + ' option ' + value, function($window, $document, done) {
|
||||
return this.addFutureAction("select '" + this.name + "' option '" + value + "'", function($window, $document, done) {
|
||||
var select = $document.elements('select[name="$1"]', this.name);
|
||||
select.val(value);
|
||||
select.trigger('change');
|
||||
@@ -222,7 +320,7 @@ angular.scenario.dsl('select', function() {
|
||||
|
||||
chain.options = function() {
|
||||
var values = arguments;
|
||||
return this.addFutureAction('select ' + this.name + ' options ' + values, function($window, $document, done) {
|
||||
return this.addFutureAction("select '" + this.name + "' options '" + values + "'", function($window, $document, done) {
|
||||
var select = $document.elements('select[multiple][name="$1"]', this.name);
|
||||
select.val(values);
|
||||
select.trigger('change');
|
||||
@@ -238,51 +336,77 @@ angular.scenario.dsl('select', function() {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* element(selector).click() clicks an element
|
||||
* element(selector).attr(name) gets the value of an attribute
|
||||
* element(selector).attr(name, value) sets the value of an attribute
|
||||
* element(selector).val() gets the value (as defined by jQuery)
|
||||
* element(selector).val(value) sets the value (as defined by jQuery)
|
||||
* element(selector).query(fn) executes fn(selectedElements, done)
|
||||
* 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)
|
||||
*/
|
||||
angular.scenario.dsl('element', function() {
|
||||
var VALUE_METHODS = [
|
||||
'val', 'text', 'html', 'height', 'innerHeight', 'outerHeight', 'width',
|
||||
'innerWidth', 'outerWidth', 'position', 'scrollLeft', 'scrollTop', 'offset'
|
||||
];
|
||||
var chain = {};
|
||||
|
||||
chain.count = function() {
|
||||
return this.addFutureAction("element '" + this.label + "' count", function($window, $document, done) {
|
||||
try {
|
||||
done(null, $document.elements().length);
|
||||
} catch (e) {
|
||||
done(null, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
chain.click = function() {
|
||||
return this.addFutureAction('element ' + this.selector + ' click', function($window, $document, done) {
|
||||
$document.elements().trigger('click');
|
||||
done();
|
||||
return this.addFutureAction("element '" + this.label + "' click", function($window, $document, done) {
|
||||
var elements = $document.elements();
|
||||
var href = elements.attr('href');
|
||||
elements.trigger('click');
|
||||
if (href && elements[0].nodeName.toUpperCase() === 'A') {
|
||||
this.application.navigateTo(href, function() {
|
||||
done();
|
||||
}, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
chain.attr = function(name, value) {
|
||||
var futureName = 'element ' + this.selector + ' get attribute ' + name;
|
||||
if (value) {
|
||||
futureName = 'element ' + this.selector + ' set attribute ' + name + ' to ' + 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.val = function(value) {
|
||||
var futureName = 'element ' + this.selector + ' value';
|
||||
if (value) {
|
||||
futureName = 'element ' + this.selector + ' set value to ' + value;
|
||||
}
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
done(null, $document.elements().val(value));
|
||||
});
|
||||
};
|
||||
|
||||
chain.query = function(fn) {
|
||||
return this.addFutureAction('element ' + this.selector + ' custom query', function($window, $document, done) {
|
||||
return this.addFutureAction('element ' + this.label + ' custom query', function($window, $document, done) {
|
||||
fn.call(this, $document.elements(), done);
|
||||
});
|
||||
};
|
||||
|
||||
return function(selector) {
|
||||
this.dsl.using(selector);
|
||||
angular.foreach(VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(value) {
|
||||
var futureName = "element '" + this.label + "' " + methodName;
|
||||
if (angular.isDefined(value)) {
|
||||
futureName = "element '" + this.label + "' set " + methodName + " to '" + value + "'";
|
||||
}
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
var element = $document.elements();
|
||||
done(null, element[methodName].call(element, value));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return function(selector, label) {
|
||||
this.dsl.using(selector, label);
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,6 +6,10 @@ angular.scenario.matcher('toEqual', function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBe', function(expected) {
|
||||
return this.actual === expected;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeDefined', function() {
|
||||
return angular.isDefined(this.actual);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* User Interface for the Scenario Runner.
|
||||
*
|
||||
* TODO(esprehn): This should be refactored now that ObjectModel exists
|
||||
* to use angular bindings for the UI.
|
||||
*/
|
||||
angular.scenario.output('html', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
|
||||
context.append(
|
||||
'<div id="header">' +
|
||||
' <h1><span class="angular"><angular/></span>: Scenario Test Runner</h1>' +
|
||||
' <ul id="status-legend" class="status-display">' +
|
||||
' <li class="status-error">0 Errors</li>' +
|
||||
' <li class="status-failure">0 Failures</li>' +
|
||||
' <li class="status-success">0 Passed</li>' +
|
||||
' </ul>' +
|
||||
'</div>' +
|
||||
'<div id="specs">' +
|
||||
' <div class="test-children"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
runner.on('InteractiveWait', function(spec, step) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
ui.find('.test-title').
|
||||
html('waiting for you to <a href="javascript:resume()">resume</a>.');
|
||||
});
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var ui = findContext(spec);
|
||||
ui.find('> .tests').append(
|
||||
'<li class="status-pending test-it"></li>'
|
||||
);
|
||||
ui = ui.find('> .tests li:last');
|
||||
ui.append(
|
||||
'<div class="test-info">' +
|
||||
' <p class="test-title">' +
|
||||
' <span class="timer-result"></span>' +
|
||||
' <span class="test-name"></span>' +
|
||||
' </p>' +
|
||||
'</div>' +
|
||||
'<div class="scrollpane">' +
|
||||
' <ol class="test-actions"></ol>' +
|
||||
'</div>'
|
||||
);
|
||||
ui.find('> .test-info .test-name').text(spec.name);
|
||||
ui.find('> .test-info').click(function() {
|
||||
var scrollpane = ui.find('> .scrollpane');
|
||||
var actions = scrollpane.find('> .test-actions');
|
||||
var name = context.find('> .test-info .test-name');
|
||||
if (actions.find(':visible').length) {
|
||||
actions.hide();
|
||||
name.removeClass('open').addClass('closed');
|
||||
} else {
|
||||
actions.show();
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
name.removeClass('closed').addClass('open');
|
||||
}
|
||||
});
|
||||
model.getSpec(spec.id).ui = ui;
|
||||
});
|
||||
|
||||
runner.on('SpecError', function(spec, error) {
|
||||
var ui = model.getSpec(spec.id).ui;
|
||||
ui.append('<pre></pre>');
|
||||
ui.find('> pre').text(formatException(error));
|
||||
});
|
||||
|
||||
runner.on('SpecEnd', function(spec) {
|
||||
spec = model.getSpec(spec.id);
|
||||
spec.ui.removeClass('status-pending');
|
||||
spec.ui.addClass('status-' + spec.status);
|
||||
spec.ui.find("> .test-info .timer-result").text(spec.duration + "ms");
|
||||
if (spec.status === 'success') {
|
||||
spec.ui.find('> .test-info .test-name').addClass('closed');
|
||||
spec.ui.find('> .scrollpane .test-actions').hide();
|
||||
}
|
||||
updateTotals(spec.status);
|
||||
});
|
||||
|
||||
runner.on('StepBegin', function(spec, step) {
|
||||
spec = model.getSpec(spec.id);
|
||||
step = spec.getLastStep();
|
||||
spec.ui.find('> .scrollpane .test-actions').
|
||||
append('<li class="status-pending"></li>');
|
||||
step.ui = spec.ui.find('> .scrollpane .test-actions li:last');
|
||||
step.ui.append(
|
||||
'<div class="timer-result"></div>' +
|
||||
'<div class="test-title"></div>'
|
||||
);
|
||||
step.ui.find('> .test-title').text(step.name);
|
||||
var scrollpane = step.ui.parents('.scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
|
||||
runner.on('StepFailure', function(spec, step, error) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
addError(ui, step.line, error);
|
||||
});
|
||||
|
||||
runner.on('StepError', function(spec, step, error) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
addError(ui, step.line, error);
|
||||
});
|
||||
|
||||
runner.on('StepEnd', function(spec, step) {
|
||||
spec = model.getSpec(spec.id);
|
||||
step = spec.getLastStep();
|
||||
step.ui.find('.timer-result').text(step.duration + 'ms');
|
||||
step.ui.removeClass('status-pending');
|
||||
step.ui.addClass('status-' + step.status);
|
||||
var scrollpane = spec.ui.find('> .scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the context of a spec block defined by the passed definition.
|
||||
*
|
||||
* @param {Object} The definition created by the Describe object.
|
||||
*/
|
||||
function findContext(spec) {
|
||||
var currentContext = context.find('#specs');
|
||||
angular.foreach(model.getDefinitionPath(spec), function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!context.find('#' + id).length) {
|
||||
currentContext.find('> .test-children').append(
|
||||
'<div class="test-describe" id="' + id + '">' +
|
||||
' <h2></h2>' +
|
||||
' <div class="test-children"></div>' +
|
||||
' <ul class="tests"></ul>' +
|
||||
'</div>'
|
||||
);
|
||||
context.find('#' + id).find('> h2').text('describe: ' + defn.name);
|
||||
}
|
||||
currentContext = context.find('#' + id);
|
||||
});
|
||||
return context.find('#describe-' + spec.definition.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the test counter for the status.
|
||||
*
|
||||
* @param {string} the status.
|
||||
*/
|
||||
function updateTotals(status) {
|
||||
var legend = context.find('#status-legend .status-' + status);
|
||||
var parts = legend.text().split(' ');
|
||||
var value = (parts[0] * 1) + 1;
|
||||
legend.text(value + ' ' + parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error to a step.
|
||||
*
|
||||
* @param {Object} The JQuery wrapped context
|
||||
* @param {Function} fn() that should return the file/line number of the error
|
||||
* @param {Object} the error.
|
||||
*/
|
||||
function addError(context, line, error) {
|
||||
context.find('.test-title').append('<pre></pre>');
|
||||
var message = _jQuery.trim(line() + '\n\n' + formatException(error));
|
||||
context.find('.test-title pre:last').text(message);
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Generates JSON output into a context.
|
||||
*/
|
||||
angular.scenario.output('json', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
|
||||
runner.on('RunnerEnd', function() {
|
||||
context.text(angular.toJson(model.value));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Creates a global value $result with the result of the runner.
|
||||
*/
|
||||
angular.scenario.output('object', function(context, runner) {
|
||||
runner.$window.$result = new angular.scenario.ObjectModel(runner).value;
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Generates XML output into a context.
|
||||
*/
|
||||
angular.scenario.output('xml', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
var $ = function(args) {return new context.init(args);};
|
||||
runner.on('RunnerEnd', function() {
|
||||
var scenario = $('<scenario></scenario>');
|
||||
context.append(scenario);
|
||||
serializeXml(scenario, model.value);
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert the tree into XML.
|
||||
*
|
||||
* @param {Object} context jQuery context to add the XML to.
|
||||
* @param {Object} tree node to serialize
|
||||
*/
|
||||
function serializeXml(context, tree) {
|
||||
angular.foreach(tree.children, function(child) {
|
||||
var describeContext = $('<describe></describe>');
|
||||
describeContext.attr('id', child.id);
|
||||
describeContext.attr('name', child.name);
|
||||
context.append(describeContext);
|
||||
serializeXml(describeContext, child);
|
||||
});
|
||||
var its = $('<its></its>');
|
||||
context.append(its);
|
||||
angular.foreach(tree.specs, function(spec) {
|
||||
var it = $('<it></it>');
|
||||
it.attr('id', spec.id);
|
||||
it.attr('name', spec.name);
|
||||
it.attr('duration', spec.duration);
|
||||
it.attr('status', spec.status);
|
||||
its.append(it);
|
||||
angular.foreach(spec.steps, function(step) {
|
||||
var stepContext = $('<step></step>');
|
||||
stepContext.attr('name', step.name);
|
||||
stepContext.attr('duration', step.duration);
|
||||
stepContext.attr('status', step.status);
|
||||
it.append(stepContext);
|
||||
if (step.error) {
|
||||
var error = $('<error></error');
|
||||
stepContext.append(error);
|
||||
error.text(formatException(stepContext.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+21
-11
@@ -16,12 +16,14 @@ angularServiceInject("$document", function(window){
|
||||
angularServiceInject("$location", function(browser) {
|
||||
var scope = this,
|
||||
location = {toString:toString, update:update, updateHash: updateHash},
|
||||
lastLocationHref = browser.getUrl(),
|
||||
lastBrowserUrl = browser.getUrl(),
|
||||
lastLocationHref,
|
||||
lastLocationHash;
|
||||
|
||||
browser.addPollFn(function(){
|
||||
if (lastLocationHref !== browser.getUrl()) {
|
||||
update(lastLocationHref = browser.getUrl());
|
||||
browser.addPollFn(function() {
|
||||
if (lastBrowserUrl != browser.getUrl()) {
|
||||
update(lastBrowserUrl = browser.getUrl());
|
||||
updateLastLocation();
|
||||
scope.$eval();
|
||||
}
|
||||
});
|
||||
@@ -29,8 +31,8 @@ angularServiceInject("$location", function(browser) {
|
||||
this.$onEval(PRIORITY_FIRST, updateBrowser);
|
||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||
|
||||
update(lastLocationHref);
|
||||
lastLocationHash = location.hash;
|
||||
update(lastBrowserUrl);
|
||||
updateLastLocation();
|
||||
|
||||
return location;
|
||||
|
||||
@@ -130,6 +132,14 @@ angularServiceInject("$location", function(browser) {
|
||||
update(location.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update information about last location
|
||||
*/
|
||||
function updateLastLocation() {
|
||||
lastLocationHref = location.href;
|
||||
lastLocationHash = location.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* If location has changed, update the browser
|
||||
* This method is called at the end of $eval() phase
|
||||
@@ -137,9 +147,9 @@ angularServiceInject("$location", function(browser) {
|
||||
function updateBrowser() {
|
||||
updateLocation();
|
||||
|
||||
if (location.href != lastLocationHref) {
|
||||
browser.setUrl(lastLocationHref = location.href);
|
||||
lastLocationHash = location.hash;
|
||||
if (location.href != lastLocationHref) {
|
||||
browser.setUrl(lastBrowserUrl = location.href);
|
||||
updateLastLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +190,7 @@ angularServiceInject("$location", function(browser) {
|
||||
var match = URL_MATCH.exec(href);
|
||||
|
||||
if (match) {
|
||||
loc.href = href.replace('#$', '');
|
||||
loc.href = href.replace(/#$/, '');
|
||||
loc.protocol = match[1];
|
||||
loc.host = match[3] || '';
|
||||
loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || _null;
|
||||
@@ -641,4 +651,4 @@ angularServiceInject('$cookieStore', function($store) {
|
||||
}
|
||||
};
|
||||
|
||||
}, ['$cookies'], EAGER_PUBLISHED);
|
||||
}, ['$cookies']);
|
||||
|
||||
+23
-5
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
function modelAccessor(scope, element) {
|
||||
var expr = element.attr('name');
|
||||
if (!expr) throw "Required field 'name' not found.";
|
||||
@@ -203,10 +207,6 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
||||
lastValue = model.get();
|
||||
scope.$tryEval(action, element);
|
||||
scope.$root.$eval();
|
||||
// if we have noop initFn than we are just a button,
|
||||
// therefore we want to prevent default action
|
||||
if(initFn == noop)
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
function updateView(){
|
||||
@@ -244,6 +244,16 @@ angularWidget('option', function(){
|
||||
});
|
||||
|
||||
|
||||
/*ng:doc
|
||||
* @type widget
|
||||
* @name ng:include
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* @scenario
|
||||
*/
|
||||
angularWidget('ng:include', function(element){
|
||||
var compiler = this,
|
||||
srcExp = element.attr("src"),
|
||||
@@ -256,11 +266,19 @@ angularWidget('ng:include', function(element){
|
||||
return extend(function(xhr, element){
|
||||
var scope = this, childScope;
|
||||
var changeCounter = 0;
|
||||
var preventRecursion = false;
|
||||
function incrementChange(){ changeCounter++;}
|
||||
this.$watch(srcExp, incrementChange);
|
||||
this.$watch(scopeExp, incrementChange);
|
||||
scope.$onEval(function(){
|
||||
if (childScope) childScope.$eval();
|
||||
if (childScope && !preventRecursion) {
|
||||
preventRecursion = true;
|
||||
try {
|
||||
childScope.$eval();
|
||||
} finally {
|
||||
preventRecursion = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.$watch(function(){return changeCounter;}, function(){
|
||||
var src = this.$eval(srcExp),
|
||||
|
||||
+31
-1
@@ -13,6 +13,15 @@ describe('Angular', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('case', function(){
|
||||
it('should change case', function(){
|
||||
expect(lowercase('ABC90')).toEqual('abc90');
|
||||
expect(manualLowercase('ABC90')).toEqual('abc90');
|
||||
expect(uppercase('abc90')).toEqual('ABC90');
|
||||
expect(manualUppercase('abc90')).toEqual('ABC90');
|
||||
});
|
||||
});
|
||||
|
||||
describe("copy", function(){
|
||||
it("should return same object", function (){
|
||||
var obj = {};
|
||||
@@ -115,7 +124,7 @@ describe('toKeyValue', function() {
|
||||
toEqual('escaped%20key=escaped%20value');
|
||||
expect(toKeyValue({emptyKey: ''})).toEqual('emptyKey=');
|
||||
});
|
||||
|
||||
|
||||
it('should parse true values into flags', function() {
|
||||
expect(toKeyValue({flag1: true, key: 'value', flag2: true})).toEqual('flag1&key=value&flag2');
|
||||
});
|
||||
@@ -187,6 +196,27 @@ describe ('rngScript', function() {
|
||||
expect('my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
|
||||
expect('foo/../my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario.js', function() {
|
||||
expect('angular-scenario.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario-0.9.0(.min).js', function() {
|
||||
expect('angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario-0.9.0-de0a8612(.min).js', function() {
|
||||
expect('angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -189,6 +189,10 @@ describe('api', function(){
|
||||
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"));
|
||||
});
|
||||
|
||||
it('StringFromUTC', function(){
|
||||
var date = angular.String.toDate("2003-09-10T13:02:03Z");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
|
||||
@@ -46,6 +46,7 @@ describe('compiler', function(){
|
||||
var init = template(e).$init;
|
||||
expect(log).toEqual("found");
|
||||
init();
|
||||
expect(e.hasClass('ng-directive')).toEqual(true);
|
||||
expect(log).toEqual("found:init");
|
||||
});
|
||||
|
||||
@@ -102,12 +103,13 @@ describe('compiler', function(){
|
||||
}
|
||||
});
|
||||
var scope = compile('before<span>middle</span>after');
|
||||
expect(lowercase(scope.$element[0].innerHTML)).toEqual('before<span hello="middle">replaced</span>after');
|
||||
expect(sortedHtml(scope.$element[0], true)).toEqual('<div>before<span class="ng-directive" hello="middle">replaced</span>after</div>');
|
||||
expect(log).toEqual("hello middle");
|
||||
});
|
||||
|
||||
it('should replace widgets', function(){
|
||||
widgets['NG:BUTTON'] = function(element) {
|
||||
expect(element.hasClass('ng-widget')).toEqual(true);
|
||||
element.replaceWith('<div>button</div>');
|
||||
return function(element) {
|
||||
log += 'init';
|
||||
@@ -120,6 +122,8 @@ describe('compiler', function(){
|
||||
|
||||
it('should use the replaced element after calling widget', function(){
|
||||
widgets['H1'] = function(element) {
|
||||
// HTML elements which are augmented by acting as widgets, should not be marked as so
|
||||
expect(element.hasClass('ng-widget')).toEqual(false);
|
||||
var span = angular.element('<span>{{1+2}}</span>');
|
||||
element.replaceWith(span);
|
||||
this.descend(true);
|
||||
|
||||
+27
-24
@@ -74,8 +74,8 @@ describe('filter', function(){
|
||||
});
|
||||
|
||||
describe('Linky', function() {
|
||||
var linky = filter.linky;
|
||||
it('should do basic filter', function(){
|
||||
var linky = filter.linky;
|
||||
assertEquals(
|
||||
'<a href="http://ab/">http://ab/</a> ' +
|
||||
'(<a href="http://a/">http://a/</a>) ' +
|
||||
@@ -84,26 +84,25 @@ describe('filter', function(){
|
||||
linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html);
|
||||
assertEquals(undefined, linky(undefined));
|
||||
});
|
||||
|
||||
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).
|
||||
toEqual('send email to <a href="mailto:me@example.com">me@example.com</a>, but');
|
||||
});
|
||||
});
|
||||
|
||||
describe('date', function(){
|
||||
var morning = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
var midnight = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
var noon = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
morning.setHours(7);
|
||||
noon.setHours(12);
|
||||
midnight.setHours(0);
|
||||
|
||||
//butt-ugly hack: force the date to be 2pm PDT for locale testing
|
||||
morning.getTimezoneOffset =
|
||||
noon.getTimezoneOffset =
|
||||
midnight.getTimezoneOffset =
|
||||
function() { return 7 * 60; };
|
||||
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
|
||||
|
||||
|
||||
it('should ignore falsy inputs', function() {
|
||||
expect(filter.date(null)).toEqual(null);
|
||||
expect(filter.date('')).toEqual('');
|
||||
expect(filter.date(123)).toEqual(123);
|
||||
});
|
||||
|
||||
it('should do basic filter', function() {
|
||||
@@ -111,19 +110,23 @@ describe('filter', function(){
|
||||
expect(filter.date(noon, '')).toEqual(noon.toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should accept format', function() {
|
||||
expect(filter.date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8am0700');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08am0700');
|
||||
|
||||
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08pm0700');
|
||||
|
||||
it('should accept number or number string representing milliseconds as input', function() {
|
||||
expect(filter.date(noon.getTime())).toEqual(noon.toLocaleDateString());
|
||||
expect(filter.date(noon.getTime() + "")).toEqual(noon.toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should accept format', function() {
|
||||
expect(filter.date(morning, "yy-MM-dd HH:mm:ss")).
|
||||
toEqual('10-09-03 07:05:08');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8am0500');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08am0500');
|
||||
|
||||
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08pm0500');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ describe('json', function(){
|
||||
assertEquals('[1,"b"]', toJson([1,"b"]));
|
||||
});
|
||||
|
||||
it('should parse RegExp', function() {
|
||||
assertEquals('"/foo/"', toJson(/foo/));
|
||||
assertEquals('[1,"/foo/"]', toJson([1,new RegExp("foo")]));
|
||||
});
|
||||
|
||||
it('should parse IgnoreFunctions', function() {
|
||||
assertEquals('[null,1]', toJson([function(){},1]));
|
||||
assertEquals('{}', toJson({a:function(){}}));
|
||||
|
||||
Vendored
+102
@@ -162,3 +162,105 @@ MockBrowser.prototype = {
|
||||
angular.service('$browser', function(){
|
||||
return new MockBrowser();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Mock of the Date type which has its timezone specified via constroctor arg.
|
||||
*
|
||||
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
|
||||
* offset, so that we can test code that depends on local timezone settings without dependency on
|
||||
* the time zone settings of the machine where the code is running.
|
||||
*
|
||||
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
|
||||
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
|
||||
*
|
||||
* @example
|
||||
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
* newYearInBratislava.getTimezoneOffset() => -60;
|
||||
* newYearInBratislava.getFullYear() => 2010;
|
||||
* newYearInBratislava.getMonth() => 0;
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
*
|
||||
*
|
||||
* !!!! WARNING !!!!!
|
||||
* This is not a complete Date object so only methods that were implemented can be called safely.
|
||||
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
|
||||
*
|
||||
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
|
||||
* incomplete we might be missing some non-standard methods. This can result in errors like:
|
||||
* "Date.prototype.foo called on incompatible Object".
|
||||
*/
|
||||
function TzDate(offset, timestamp) {
|
||||
if (angular.isString(timestamp)) {
|
||||
var tsStr = timestamp;
|
||||
timestamp = angular.String.toDate(timestamp).getTime();
|
||||
if (isNaN(timestamp))
|
||||
throw {
|
||||
name: "Illegal Argument",
|
||||
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
|
||||
};
|
||||
}
|
||||
|
||||
var localOffset = new Date(timestamp).getTimezoneOffset();
|
||||
this.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
|
||||
this.date = new Date(timestamp + this.offsetDiff);
|
||||
|
||||
this.getTime = function() {
|
||||
return this.date.getTime() - this.offsetDiff;
|
||||
};
|
||||
|
||||
this.toLocaleDateString = function() {
|
||||
return this.date.toLocaleDateString();
|
||||
};
|
||||
|
||||
this.getFullYear = function() {
|
||||
return this.date.getFullYear();
|
||||
};
|
||||
|
||||
this.getMonth = function() {
|
||||
return this.date.getMonth();
|
||||
};
|
||||
|
||||
this.getDate = function() {
|
||||
return this.date.getDate();
|
||||
};
|
||||
|
||||
this.getHours = function() {
|
||||
return this.date.getHours();
|
||||
};
|
||||
|
||||
this.getMinutes = function() {
|
||||
return this.date.getMinutes();
|
||||
};
|
||||
|
||||
this.getSeconds = function() {
|
||||
return this.date.getSeconds();
|
||||
};
|
||||
|
||||
this.getTimezoneOffset = function() {
|
||||
return offset * 60;
|
||||
};
|
||||
|
||||
//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',
|
||||
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
|
||||
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||||
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
|
||||
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
|
||||
|
||||
angular.foreach(unimplementedMethods, function(methodName) {
|
||||
this[methodName] = function() {
|
||||
throw {
|
||||
name: "MethodNotImplemented",
|
||||
message: "Method '" + methodName + "' is not implemented in the TzDate mock"
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//make "tzDateInstance instanceof Date" return true
|
||||
TzDate.prototype = Date.prototype;
|
||||
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
describe('TzDate', function() {
|
||||
|
||||
function minutes(min) {
|
||||
return min*60*1000;
|
||||
}
|
||||
|
||||
it('should take millis as constructor argument', function() {
|
||||
expect(new TzDate(0, 0).getTime()).toBe(0);
|
||||
expect(new TzDate(0, 1283555108000).getTime()).toBe(1283555108000);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getLocalDateString method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.toLocaleDateString()).toMatch('1970');
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.toLocaleDateString()).toMatch('1970');
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.toLocaleDateString()).toMatch('1969');
|
||||
});
|
||||
|
||||
|
||||
it('should fake getHours method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.getHours()).toBe(3);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.getHours()).toBe(0);
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.getHours()).toMatch(21);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getMinutes method', function() {
|
||||
//0:15 in -3h
|
||||
var t0 = new TzDate(-3, minutes(15));
|
||||
expect(t0.getMinutes()).toBe(15);
|
||||
|
||||
//0:15 in -3.25h
|
||||
var t0a = new TzDate(-3.25, minutes(15));
|
||||
expect(t0a.getMinutes()).toBe(30);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, minutes(0));
|
||||
expect(t1.getMinutes()).toBe(0);
|
||||
|
||||
//0:15 in +0h
|
||||
var t1a = new TzDate(0, minutes(15));
|
||||
expect(t1a.getMinutes()).toBe(15);
|
||||
|
||||
//0:15 in +3h
|
||||
var t2 = new TzDate(3, minutes(15));
|
||||
expect(t2.getMinutes()).toMatch(15);
|
||||
|
||||
//0:15 in +3.25h
|
||||
var t2a = new TzDate(3.25, minutes(15));
|
||||
expect(t2a.getMinutes()).toMatch(0);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getSeconds method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.getSeconds()).toBe(0);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.getSeconds()).toBe(0);
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.getSeconds()).toMatch(0);
|
||||
});
|
||||
|
||||
|
||||
it('should create a date representing new year in Bratislava', function() {
|
||||
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
|
||||
expect(newYearInBratislava.getFullYear()).toBe(2010);
|
||||
expect(newYearInBratislava.getMonth()).toBe(0);
|
||||
expect(newYearInBratislava.getDate()).toBe(1);
|
||||
expect(newYearInBratislava.getHours()).toBe(0);
|
||||
expect(newYearInBratislava.getMinutes()).toBe(0);
|
||||
});
|
||||
});
|
||||
+30
-7
@@ -35,6 +35,7 @@ describe("directives", function(){
|
||||
expect(element.text()).toEqual('');
|
||||
scope.a = 'misko';
|
||||
scope.$eval();
|
||||
expect(element.hasClass('ng-binding')).toEqual(true);
|
||||
expect(element.text()).toEqual('misko');
|
||||
});
|
||||
|
||||
@@ -50,11 +51,18 @@ describe("directives", function(){
|
||||
|
||||
it('should set html', function() {
|
||||
var scope = compile('<div ng:bind="html|html"></div>');
|
||||
scope.html = '<div>hello</div>';
|
||||
scope.html = '<div unknown>hello</div>';
|
||||
scope.$eval();
|
||||
expect(lowercase(element.html())).toEqual('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('should set unsafe html', function() {
|
||||
var scope = compile('<div ng:bind="html|html:\'unsafe\'"></div>');
|
||||
scope.html = '<div onclick="">hello</div>';
|
||||
scope.$eval();
|
||||
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
});
|
||||
|
||||
it('should set element element', function() {
|
||||
angularFilter.myElement = function() {
|
||||
return jqLite('<a>hello</a>');
|
||||
@@ -80,6 +88,7 @@ describe("directives", function(){
|
||||
var scope = compile('<div ng:bind-template="Hello {{name}}!"></div>');
|
||||
scope.$set('name', 'Misko');
|
||||
scope.$eval();
|
||||
expect(element.hasClass('ng-binding')).toEqual(true);
|
||||
expect(element.text()).toEqual('Hello Misko!');
|
||||
});
|
||||
|
||||
@@ -172,7 +181,7 @@ describe("directives", function(){
|
||||
});
|
||||
|
||||
describe('ng:click', function(){
|
||||
it('should fire event', function(){
|
||||
it('should get called on a click', function(){
|
||||
var scope = compile('<div ng:click="clicked = true"></div>');
|
||||
scope.$eval();
|
||||
expect(scope.$get('clicked')).toBeFalsy();
|
||||
@@ -184,14 +193,28 @@ describe("directives", function(){
|
||||
it('should stop event propagation', function() {
|
||||
var scope = compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>');
|
||||
scope.$eval();
|
||||
expect(scope.$get('outer')).not.toBeDefined();
|
||||
expect(scope.$get('inner')).not.toBeDefined();
|
||||
expect(scope.outer).not.toBeDefined();
|
||||
expect(scope.inner).not.toBeDefined();
|
||||
|
||||
var innerDiv = jqLite(element.children()[0]);
|
||||
var innerDiv = element.children()[0];
|
||||
|
||||
browserTrigger(innerDiv, 'click');
|
||||
expect(scope.$get('outer')).not.toBeDefined();
|
||||
expect(scope.$get('inner')).toEqual(true);
|
||||
expect(scope.outer).not.toBeDefined();
|
||||
expect(scope.inner).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ng:submit', function() {
|
||||
it('should get called on form submit', function() {
|
||||
var scope = compile('<form action="" ng:submit="submitted = true">' +
|
||||
'<input type="submit"/>' +
|
||||
'</form>');
|
||||
scope.$eval();
|
||||
expect(scope.submitted).not.toBeDefined();
|
||||
|
||||
browserTrigger(element.children()[0]);
|
||||
expect(scope.submitted).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Test Runner</title>
|
||||
<link rel="stylesheet" type="text/css" href="../lib/jasmine-1.0.1/jasmine.css">
|
||||
<script type="text/javascript" src="../lib/jasmine-1.0.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="../lib/jasmine-1.0.1/jasmine-html.js"></script>
|
||||
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript"> var _jQuery = $;</script>
|
||||
|
||||
<script type="text/javascript" src="../src/Angular.js"></script>
|
||||
<script type="text/javascript" src="../src/JSON.js"></script>
|
||||
<script type="text/javascript" src="../src/Compiler.js"></script>
|
||||
<script type="text/javascript" src="../src/Scope.js"></script>
|
||||
<script type="text/javascript" src="../src/Injector.js"></script>
|
||||
<script type="text/javascript" src="../src/jqLite.js"></script>
|
||||
<script type="text/javascript" src="../src/parser.js"></script>
|
||||
<script type="text/javascript" src="../src/Resource.js"></script>
|
||||
<script type="text/javascript" src="../src/Browser.js"></script>
|
||||
<script type="text/javascript" src="../src/AngularPublic.js"></script>
|
||||
<script type="text/javascript" src="../src/services.js"></script>
|
||||
<script type="text/javascript" src="../src/apis.js"></script>
|
||||
<script type="text/javascript" src="../src/filters.js"></script>
|
||||
<script type="text/javascript" src="../src/formatters.js"></script>
|
||||
<script type="text/javascript" src="../src/validators.js"></script>
|
||||
<script type="text/javascript" src="../src/directives.js"></script>
|
||||
<script type="text/javascript" src="../src/markups.js"></script>
|
||||
<script type="text/javascript" src="../src/widgets.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../src/scenario/Scenario.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Application.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Describe.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Future.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/HtmlUI.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Runner.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/SpecRunner.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/dsl.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/matchers.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/ObjectModel.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../src/scenario/output/Html.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Object.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Json.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Xml.js"></script>
|
||||
|
||||
<script type="text/javascript" src="angular-mocks.js"></script>
|
||||
<script type="text/javascript" src="../test/scenario/mocks.js"></script>
|
||||
<script type="text/javascript" src="testabilityPatch.js"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script type="text/javascript">
|
||||
describe('manual', function(){
|
||||
var compile, model, element;
|
||||
|
||||
beforeEach(function() {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
compile = function(html) {
|
||||
element = jqLite(html);
|
||||
model = compiler.compile(element)(element);
|
||||
model.$init();
|
||||
return model;
|
||||
};
|
||||
});
|
||||
|
||||
it('should get called on form submit', function() {
|
||||
var scope = compile('<form action="" ng:submit="submitted = true">' +
|
||||
'<input type="submit"/>' +
|
||||
'</form>');
|
||||
scope.$eval();
|
||||
expect(scope.submitted).not.toBeDefined();
|
||||
|
||||
browserTrigger(element.children()[0]);
|
||||
expect(scope.submitted).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('angular.scenario.output.json', function() {
|
||||
var output, context;
|
||||
var runner, $window;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
context = _jQuery('<div>text</div>');
|
||||
$(document.body).append(context);
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
output = angular.scenario.output.xml(context, runner);
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe'
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
});
|
||||
|
||||
it('should create XML nodes for object model', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
expect(_jQuery(context).find('it').attr('status')).toEqual('success');
|
||||
expect(_jQuery(context).find('it step').attr('status')).toEqual('success');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
|
||||
function run(){
|
||||
jasmine.getEnv().execute();
|
||||
}
|
||||
run();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
describe('HTML', function(){
|
||||
|
||||
function expectHTML(html) {
|
||||
return expect(new HTML(html).get());
|
||||
}
|
||||
|
||||
it('should echo html', function(){
|
||||
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
|
||||
toEqual('hello<b class="1\'23" align="""">world</b>.');
|
||||
});
|
||||
|
||||
it('should remove script', function(){
|
||||
expectHTML('a<SCRIPT>evil< / scrIpt >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove nested script', function(){
|
||||
expectHTML('a< SCRIPT >A< SCRIPT >evil< / scrIpt >B< / scrIpt >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove attrs', function(){
|
||||
expectHTML('a<div style="abc">b</div>c').toEqual('a<div>b</div>c');
|
||||
});
|
||||
|
||||
it('should remove style', function(){
|
||||
expectHTML('a<STyle>evil</stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove script and style', function(){
|
||||
expectHTML('a<STyle>evil<script></script></stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove double nested script', function(){
|
||||
expectHTML('a<SCRIPT>ev<script>evil</sCript>il</scrIpt>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove unknown tag names', function(){
|
||||
expectHTML('a<xxx><B>b</B></xxx>c').toEqual('a<b>b</b>c');
|
||||
});
|
||||
|
||||
it('should remove unsafe value', function(){
|
||||
expectHTML('<a href="javascript:alert()">').toEqual('<a></a>');
|
||||
});
|
||||
|
||||
it('should handle self closed elements', function(){
|
||||
expectHTML('a<hr/>c').toEqual('a<hr/>c');
|
||||
});
|
||||
|
||||
it('should handle namespace', function(){
|
||||
expectHTML('a<my:hr/><my:div>b</my:div>c').toEqual('abc');
|
||||
});
|
||||
|
||||
it('should handle improper html', function(){
|
||||
expectHTML('< div id="</div>" alt=abc href=\'"\' >text< /div>').
|
||||
toEqual('<div id="</div>" alt="abc" href=""">text</div>');
|
||||
});
|
||||
|
||||
it('should handle improper html2', function(){
|
||||
expectHTML('< div id="</div>" / >').
|
||||
toEqual('<div id="</div>"/>');
|
||||
});
|
||||
|
||||
describe('htmlSanitizerWriter', function(){
|
||||
var writer, html;
|
||||
beforeEach(function(){
|
||||
html = '';
|
||||
writer = htmlSanitizeWriter({push:function(text){html+=text;}});
|
||||
});
|
||||
|
||||
it('should write basic HTML', function(){
|
||||
writer.chars('before');
|
||||
writer.start('div', {id:'123'}, false);
|
||||
writer.chars('in');
|
||||
writer.end('div');
|
||||
writer.chars('after');
|
||||
|
||||
expect(html).toEqual('before<div id="123">in</div>after');
|
||||
});
|
||||
|
||||
it('should escape text nodes', function(){
|
||||
writer.chars('a<div>&</div>c');
|
||||
expect(html).toEqual('a<div>&</div>c');
|
||||
});
|
||||
|
||||
it('should not double escape entities', function(){
|
||||
writer.chars(' ><');
|
||||
expect(html).toEqual(' ><');
|
||||
});
|
||||
|
||||
it('should escape IE script', function(){
|
||||
writer.chars('&{}');
|
||||
expect(html).toEqual('&{}');
|
||||
});
|
||||
|
||||
it('should escape attributes', function(){
|
||||
writer.start('div', {id:'\"\'<>'});
|
||||
expect(html).toEqual('<div id=""\'<>">');
|
||||
});
|
||||
|
||||
it('should ignore missformed elements', function(){
|
||||
writer.start('d>i&v', {});
|
||||
expect(html).toEqual('');
|
||||
});
|
||||
|
||||
it('should ignore unknown attributes', function(){
|
||||
writer.start('div', {unknown:""});
|
||||
expect(html).toEqual('<div>');
|
||||
});
|
||||
|
||||
describe('javascript URL attribute', function(){
|
||||
beforeEach(function(){
|
||||
this.addMatchers({
|
||||
toBeValidUrl: function(){
|
||||
return !isJavaScriptUrl(this.actual);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore javascript:', function(){
|
||||
expect('JavaScript:abc').not.toBeValidUrl();
|
||||
expect(' \n Java\n Script:abc').not.toBeValidUrl();
|
||||
expect('JavaScript/my.js').toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore dec encoded javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore decimal with leading 0 encodede javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore hex encoded javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore hex encoded whitespace javascript:', function(){
|
||||
expect('jav	ascript:alert("A");').not.toBeValidUrl();
|
||||
expect('jav
ascript:alert("B");').not.toBeValidUrl();
|
||||
expect('jav
 ascript:alert("C");').not.toBeValidUrl();
|
||||
expect('jav\u0000ascript:alert("D");').not.toBeValidUrl();
|
||||
expect('java\u0000\u0000script:alert("D");').not.toBeValidUrl();
|
||||
expect('  java\u0000\u0000script:alert("D");').not.toBeValidUrl();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,16 +1,27 @@
|
||||
describe('angular.scenario.Application', function() {
|
||||
var $window;
|
||||
var app, frames;
|
||||
|
||||
function callLoadHandlers(app) {
|
||||
var handlers = app.getFrame_().data('events').load;
|
||||
expect(handlers).toBeDefined();
|
||||
expect(handlers.length).toEqual(1);
|
||||
handlers[0].handler();
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
frames = _jQuery("<div></div>");
|
||||
app = new angular.scenario.Application(frames);
|
||||
app.checkUrlStatus_ = function(url, callback) {
|
||||
callback.call(this);
|
||||
};
|
||||
});
|
||||
|
||||
it('should return new $window and $document after navigate', function() {
|
||||
var called;
|
||||
var testWindow, testDocument, counter = 0;
|
||||
app.navigateTo = noop;
|
||||
app.getWindow = function() {
|
||||
return {x:counter++, document:{x:counter++}};
|
||||
app.getWindow_ = function() {
|
||||
return {x:counter++, document:{x:counter++}};
|
||||
};
|
||||
app.navigateTo('http://www.google.com/');
|
||||
app.executeAction(function($document, $window) {
|
||||
@@ -21,56 +32,186 @@ describe('angular.scenario.Application', function() {
|
||||
app.executeAction(function($window, $document) {
|
||||
expect($window).not.toEqual(testWindow);
|
||||
expect($document).not.toEqual(testDocument);
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should execute callback with correct arguments', function() {
|
||||
var called;
|
||||
var testWindow = {document: {}};
|
||||
app.getWindow = function() {
|
||||
return testWindow;
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($window, $document) {
|
||||
expect(this).toEqual(app);
|
||||
expect($document).toEqual(_jQuery($window.document));
|
||||
expect($window).toEqual(testWindow);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new iframe each time', function() {
|
||||
app.navigateTo('about:blank');
|
||||
var frame = app.getFrame();
|
||||
frame.attr('test', true);
|
||||
app.navigateTo('about:blank');
|
||||
expect(app.getFrame().attr('test')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should URL description bar', function() {
|
||||
app.navigateTo('about:blank');
|
||||
var anchor = frames.find('> h2 a');
|
||||
expect(anchor.attr('href')).toEqual('about:blank');
|
||||
expect(anchor.text()).toEqual('about:blank');
|
||||
});
|
||||
|
||||
it('should call onload handler when frame loads', function() {
|
||||
var called;
|
||||
app.getFrame = function() {
|
||||
// Mock a little jQuery
|
||||
var result = {
|
||||
remove: function() {
|
||||
return result;
|
||||
},
|
||||
attr: function(key, value) {
|
||||
return (!value) ? 'attribute value' : result;
|
||||
},
|
||||
load: function() {
|
||||
called = true;
|
||||
}
|
||||
};
|
||||
return result;
|
||||
};
|
||||
app.navigateTo('about:blank', function() {
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should use a new iframe each time', function() {
|
||||
app.navigateTo('http://localhost/');
|
||||
var frame = app.getFrame_();
|
||||
frame.attr('test', true);
|
||||
app.navigateTo('http://localhost/');
|
||||
expect(app.getFrame_().attr('test')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call error handler if document not accessible', function() {
|
||||
var called;
|
||||
app.getWindow_ = function() {
|
||||
return {};
|
||||
};
|
||||
app.navigateTo('http://localhost/', angular.noop, function(error) {
|
||||
expect(error).toMatch(/Sandbox Error/);
|
||||
called = true;
|
||||
});
|
||||
callLoadHandlers(app);
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call error handler if navigating to about:blank', function() {
|
||||
var called;
|
||||
app.navigateTo('about:blank', angular.noop, function(error) {
|
||||
expect(error).toMatch(/Sandbox Error/);
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call error handler if status check fails', function() {
|
||||
app.checkUrlStatus_ = function(url, callback) {
|
||||
callback.call(this, 'Example Error');
|
||||
};
|
||||
app.navigateTo('http://localhost/', angular.noop, function(error) {
|
||||
expect(error).toEqual('Example Error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide old iframes and navigate to about:blank', function() {
|
||||
app.navigateTo('http://localhost/#foo');
|
||||
app.navigateTo('http://localhost/#bar');
|
||||
var iframes = frames.find('iframe');
|
||||
expect(iframes.length).toEqual(2);
|
||||
expect(iframes[0].src).toEqual('about:blank');
|
||||
expect(iframes[1].src).toEqual('http://localhost/#bar');
|
||||
expect(_jQuery(iframes[0]).css('display')).toEqual('none');
|
||||
});
|
||||
|
||||
it('should URL update description bar', function() {
|
||||
app.navigateTo('http://localhost/');
|
||||
var anchor = frames.find('> h2 a');
|
||||
expect(anchor.attr('href')).toEqual('http://localhost/');
|
||||
expect(anchor.text()).toEqual('http://localhost/');
|
||||
});
|
||||
|
||||
it('should call onload handler when frame loads', function() {
|
||||
var called;
|
||||
app.getWindow_ = function() {
|
||||
return {document: {}};
|
||||
};
|
||||
app.navigateTo('http://localhost/', function($window, $document) {
|
||||
called = true;
|
||||
});
|
||||
callLoadHandlers(app);
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should wait for pending requests in executeAction', function() {
|
||||
var called, polled;
|
||||
var handlers = [];
|
||||
var testWindow = {
|
||||
document: _jQuery('<div class="test-foo"></div>'),
|
||||
angular: {
|
||||
service: {}
|
||||
}
|
||||
};
|
||||
testWindow.angular.service.$browser = function() {
|
||||
return {
|
||||
poll: function() {
|
||||
polled = true;
|
||||
},
|
||||
notifyWhenNoOutstandingRequests: function(fn) {
|
||||
handlers.push(fn);
|
||||
}
|
||||
};
|
||||
};
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($window, $document) {
|
||||
expect($window).toEqual(testWindow);
|
||||
expect($document).toBeDefined();
|
||||
expect($document[0].className).toEqual('test-foo');
|
||||
});
|
||||
expect(polled).toBeTruthy();
|
||||
expect(handlers.length).toEqual(1);
|
||||
handlers[0]();
|
||||
});
|
||||
|
||||
describe('jQuery ajax', function() {
|
||||
var options;
|
||||
var response;
|
||||
var jQueryAjax;
|
||||
|
||||
beforeEach(function() {
|
||||
response = {
|
||||
status: 200,
|
||||
statusText: 'OK'
|
||||
};
|
||||
jQueryAjax = _jQuery.ajax;
|
||||
_jQuery.ajax = function(opts) {
|
||||
options = opts;
|
||||
opts.complete.call(this, response);
|
||||
};
|
||||
app.checkUrlStatus_ = angular.scenario.Application.
|
||||
prototype.checkUrlStatus_;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
_jQuery.ajax = jQueryAjax;
|
||||
});
|
||||
|
||||
it('should perform a HEAD request to verify file existence', function() {
|
||||
app.navigateTo('http://www.google.com/', angular.noop, angular.noop);
|
||||
expect(options.type).toEqual('HEAD');
|
||||
expect(options.url).toEqual('http://www.google.com/');
|
||||
});
|
||||
|
||||
it('should call error handler if status code is less than 200', function() {
|
||||
var finished;
|
||||
response.status = 199;
|
||||
response.statusText = 'Error Message';
|
||||
app.navigateTo('http://localhost/', angular.noop, function(error) {
|
||||
expect(error).toEqual('199 Error Message');
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call error handler if status code is greater than 299', function() {
|
||||
var finished;
|
||||
response.status = 300;
|
||||
response.statusText = 'Error';
|
||||
app.navigateTo('http://localhost/', angular.noop, function(error) {
|
||||
expect(error).toEqual('300 Error');
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call error handler if status code is 0 for sandbox error', function() {
|
||||
var finished;
|
||||
response.status = 0;
|
||||
response.statusText = '';
|
||||
app.navigateTo('http://localhost/', angular.noop, function(error) {
|
||||
expect(error).toEqual('Sandbox Error: Cannot access http://localhost/');
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,21 +4,21 @@ describe('angular.scenario.Describe', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
root = new angular.scenario.Describe();
|
||||
|
||||
|
||||
/**
|
||||
* Simple callback logging system. Use to assert proper order of calls.
|
||||
*/
|
||||
log = function(text) {
|
||||
log.text = log.text + text;
|
||||
log = function(text) {
|
||||
log.text = log.text + text;
|
||||
};
|
||||
log.fn = function(text) {
|
||||
return function(done){
|
||||
log(text);
|
||||
(done || angular.noop)();
|
||||
return function(done){
|
||||
log(text);
|
||||
(done || angular.noop)();
|
||||
};
|
||||
};
|
||||
log.reset = function() {
|
||||
log.text = '';
|
||||
log.reset = function() {
|
||||
log.text = '';
|
||||
};
|
||||
log.reset();
|
||||
});
|
||||
@@ -50,7 +50,7 @@ describe('angular.scenario.Describe', function() {
|
||||
specs[1].after();
|
||||
expect(log.text).toEqual('{1}');
|
||||
});
|
||||
|
||||
|
||||
it('should link nested describe blocks with parent and children', function() {
|
||||
root.describe('A', function() {
|
||||
this.it('1', angular.noop);
|
||||
@@ -65,7 +65,7 @@ describe('angular.scenario.Describe', function() {
|
||||
expect(specs[2].definition.parent).toEqual(root);
|
||||
expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should not process xit and xdescribe', function() {
|
||||
root.describe('A', function() {
|
||||
this.xit('1', angular.noop);
|
||||
@@ -79,7 +79,28 @@ describe('angular.scenario.Describe', function() {
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should only return iit and ddescribe if present', function() {
|
||||
root.describe('A', function() {
|
||||
this.it('1', angular.noop);
|
||||
this.iit('2', angular.noop);
|
||||
this.describe('B', function() {
|
||||
this.it('3', angular.noop);
|
||||
this.ddescribe('C', function() {
|
||||
this.it('4', angular.noop);
|
||||
this.describe('D', function() {
|
||||
this.it('5', angular.noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(3);
|
||||
expect(specs[0].name).toEqual('5');
|
||||
expect(specs[1].name).toEqual('4');
|
||||
expect(specs[2].name).toEqual('2');
|
||||
});
|
||||
|
||||
it('should create uniqueIds in the tree', function() {
|
||||
angular.scenario.Describe.id = 0;
|
||||
var a = new angular.scenario.Describe();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user