mirror of
https://github.com/koel/koel
synced 2025-02-17 13:58:28 +00:00
More tests
This commit is contained in:
parent
ef618a611b
commit
8b7c226343
18 changed files with 806 additions and 235 deletions
|
@ -33,7 +33,7 @@
|
|||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/TestCase.php",
|
||||
"tests/E2E/TestCase.php"
|
||||
"tests/E2E"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -63,6 +63,6 @@
|
|||
"scripts": {
|
||||
"postinstall": "cross-env NODE_ENV=production && gulp --production",
|
||||
"test": "mocha --compilers js:babel-register --require resources/assets/js/tests/helper.js resources/assets/js/tests/**/*Test.js",
|
||||
"e2e": "echo 'remember to run `php artisan serve --port=8081`' && phpunit tests/e2e -c phpunit.e2e.xml"
|
||||
"e2e": "echo 'Remember to run `php artisan serve --port=8081`' && phpunit tests/e2e -c phpunit.e2e.xml"
|
||||
}
|
||||
}
|
||||
|
|
12
resources/assets/js/.idea/js.iml
generated
Normal file
12
resources/assets/js/.idea/js.iml
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
resources/assets/js/.idea/jsLibraryMappings.xml
generated
Normal file
6
resources/assets/js/.idea/jsLibraryMappings.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||
</component>
|
||||
</project>
|
16
resources/assets/js/.idea/misc.xml
generated
Normal file
16
resources/assets/js/.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<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>
|
||||
</project>
|
8
resources/assets/js/.idea/modules.xml
generated
Normal file
8
resources/assets/js/.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/js.iml" filepath="$PROJECT_DIR$/.idea/js.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
4
resources/assets/js/.idea/watcherTasks.xml
generated
Normal file
4
resources/assets/js/.idea/watcherTasks.xml
generated
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions" suppressed-tasks="Babel" />
|
||||
</project>
|
338
resources/assets/js/.idea/workspace.xml
generated
Normal file
338
resources/assets/js/.idea/workspace.xml
generated
Normal file
|
@ -0,0 +1,338 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="ab3db53b-a246-4a74-8766-88c1db2358b0" name="Default" comment="" />
|
||||
<ignored path="js.iws" />
|
||||
<ignored path=".idea/workspace.xml" />
|
||||
<ignored path="$PROJECT_DIR$/.tmp/" />
|
||||
<ignored path="$PROJECT_DIR$/temp/" />
|
||||
<ignored path="$PROJECT_DIR$/tmp/" />
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||
<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="CreatePatchCommitExecutor">
|
||||
<option name="PATCH_PATH" value="" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
|
||||
<component name="FavoritesManager">
|
||||
<favorites_list name="js" />
|
||||
</component>
|
||||
<component name="FileEditorManager">
|
||||
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||
<file leaf-file-name="login-form.vue" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/components/auth/login-form.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="465">
|
||||
<caret line="32" column="25" selection-start-line="32" selection-start-column="25" selection-end-line="32" selection-end-column="25" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="index.vue" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/components/main-wrapper/index.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="75">
|
||||
<caret line="5" column="8" selection-start-line="5" selection-start-column="8" selection-end-line="5" selection-end-column="8" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="edit-songs-form.vue" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/components/modals/edit-songs-form.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-3720">
|
||||
<caret line="13" column="15" selection-start-line="13" selection-start-column="15" selection-end-line="13" selection-end-column="15" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="album-item.vue" pinned="false" current-in-tab="true">
|
||||
<entry file="file://$PROJECT_DIR$/components/shared/album-item.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="451">
|
||||
<caret line="106" column="43" selection-start-line="106" selection-start-column="43" selection-end-line="106" selection-end-column="43" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="main.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/main.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="180">
|
||||
<caret line="15" column="16" selection-start-line="15" selection-start-column="16" selection-end-line="15" selection-end-column="16" />
|
||||
<folding>
|
||||
<element signature="e#0#22#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
</leaf>
|
||||
</component>
|
||||
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||
<component name="JsGulpfileManager">
|
||||
<detection-done>true</detection-done>
|
||||
<sorting>DEFINITION_ORDER</sorting>
|
||||
</component>
|
||||
<component name="ProjectFrameBounds">
|
||||
<option name="x" value="20" />
|
||||
<option name="y" value="43" />
|
||||
<option name="width" value="1400" />
|
||||
<option name="height" value="833" />
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<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="ProjectView">
|
||||
<navigator currentView="ProjectPane" proportions="" version="1">
|
||||
<flattenPackages />
|
||||
<showMembers />
|
||||
<showModules />
|
||||
<showLibraryContents />
|
||||
<hideEmptyPackages />
|
||||
<abbreviatePackageNames />
|
||||
<autoscrollToSource />
|
||||
<autoscrollFromSource />
|
||||
<sortByType />
|
||||
<manualOrder />
|
||||
<foldersAlwaysOnTop value="true" />
|
||||
</navigator>
|
||||
<panes>
|
||||
<pane id="Scratches" />
|
||||
<pane id="Scope" />
|
||||
<pane id="ProjectPane">
|
||||
<subPane>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="components" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="components" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="shared" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="components" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="modals" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="components" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="main-wrapper" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="js" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="components" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="auth" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
</subPane>
|
||||
</pane>
|
||||
</panes>
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="HbShouldOpenHtmlAsHb" value="" />
|
||||
<property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir="">
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="ShelveChangesManager" show_recycled="false">
|
||||
<option name="remove_strategy" value="false" />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="ab3db53b-a246-4a74-8766-88c1db2358b0" name="Default" comment="" />
|
||||
<created>1479096421308</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1479096421308</updated>
|
||||
<workItem from="1479096423357" duration="76000" />
|
||||
<workItem from="1479096510774" duration="15000" />
|
||||
<workItem from="1479096542485" duration="78000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TimeTrackingManager">
|
||||
<option name="totallyTimeSpent" value="169000" />
|
||||
</component>
|
||||
<component name="ToolWindowManager">
|
||||
<frame x="20" y="43" width="1400" height="833" extended-state="0" />
|
||||
<editor active="false" />
|
||||
<layout>
|
||||
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.17571428" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
|
||||
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
|
||||
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
|
||||
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" 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" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" 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" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||
</layout>
|
||||
</component>
|
||||
<component name="Vcs.Log.UiProperties">
|
||||
<option name="RECENTLY_FILTERED_USER_GROUPS">
|
||||
<collection />
|
||||
</option>
|
||||
<option name="RECENTLY_FILTERED_BRANCH_GROUPS">
|
||||
<collection />
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsContentAnnotationSettings">
|
||||
<option name="myLimit" value="2678400000" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager />
|
||||
<watches-manager />
|
||||
</component>
|
||||
<component name="editorHistoryManager">
|
||||
<entry file="file://$PROJECT_DIR$/components/auth/login-form.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="465">
|
||||
<caret line="32" column="25" selection-start-line="32" selection-start-column="25" selection-end-line="32" selection-end-column="25" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/main.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="180">
|
||||
<caret line="15" column="16" selection-start-line="15" selection-start-column="16" selection-end-line="15" selection-end-column="16" />
|
||||
<folding>
|
||||
<element signature="e#0#22#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/components/main-wrapper/index.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="75">
|
||||
<caret line="5" column="8" selection-start-line="5" selection-start-column="8" selection-end-line="5" selection-end-column="8" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/components/modals/edit-songs-form.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-3720">
|
||||
<caret line="13" column="15" selection-start-line="13" selection-start-column="15" selection-end-line="13" selection-end-column="15" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/components/shared/album-item.vue">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="451">
|
||||
<caret line="106" column="43" selection-start-line="106" selection-start-column="43" selection-end-line="106" selection-end-column="43" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</component>
|
||||
</project>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div id="youtube-wrapper">
|
||||
<div id="youtube-extra-wrapper">
|
||||
<template v-if="videos && videos.length">
|
||||
<a class="video" v-for="video in videos" href @click.prevent="playYouTube(video.id.videoId)">
|
||||
<div class="thumb">
|
||||
|
@ -58,7 +58,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
#youtube-wrapper {
|
||||
#youtube-extra-wrapper {
|
||||
overflow-x: hidden;
|
||||
|
||||
.video {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<section id="youTubePlayer">
|
||||
<section id="youtubeWrapper">
|
||||
<h1 class="heading"><span>YouTube Video</span></h1>
|
||||
<div id="player">
|
||||
<p class="none">Your YouTube video will be played here.<br/>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div v-show="currentView === 'details'">
|
||||
<div class="form-row" v-if="editSingle">
|
||||
<label>Title</label>
|
||||
<input type="text" v-model="formData.title">
|
||||
<input name="title" type="text" v-model="formData.title">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Artist</label>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<div class="form-row" v-show="editSingle">
|
||||
<label>Track</label>
|
||||
<input type="number" min="0" v-model="formData.track">
|
||||
<input name="track" type="number" min="0" v-model="formData.track">
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="currentView === 'lyrics' && editSingle">
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
class="song-item"
|
||||
draggable="true"
|
||||
:data-song-id="song.id"
|
||||
key="id"
|
||||
@click="$parent.rowClick(song.id, $event)"
|
||||
@click="clicked($event)"
|
||||
@dblclick.prevent="playRightAwayyyyyyy"
|
||||
@dragstart="$parent.dragStart(song.id, $event)"
|
||||
@dragleave="$parent.removeDroppableState($event)"
|
||||
@dragover.prevent="$parent.allowDrop(song.id, $event)"
|
||||
@drop.stop.prevent="$parent.handleDrop(song.id, $event)"
|
||||
@contextmenu.prevent="$parent.openContextMenu(song.id, $event)"
|
||||
:class="{ selected: selected, playing: song.playbackState === 'playing' || song.playbackState === 'paused' }"
|
||||
:class="{ selected: selected, playing: playing }"
|
||||
>
|
||||
<td class="track-number">{{ song.track || '' }}</td>
|
||||
<td class="title">{{ song.title }}</td>
|
||||
|
@ -38,6 +37,12 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
playing() {
|
||||
return this.song.playbackState === 'playing' || this.song.playbackState === 'paused';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Play the song right away.
|
||||
|
@ -67,26 +72,24 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
clicked($e) {
|
||||
this.$emit('itemClicked', this.song.id, $e);
|
||||
},
|
||||
|
||||
select() {
|
||||
this.selected = true;
|
||||
},
|
||||
|
||||
deselect() {
|
||||
this.selected = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the "selected" state of the current component.
|
||||
*/
|
||||
toggleSelectedState() {
|
||||
this.selected = !this.selected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Select the current component (apply a CSS class on its DOM).
|
||||
*/
|
||||
select() {
|
||||
this.selected = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Deselect the current component.
|
||||
*/
|
||||
deselect() {
|
||||
this.selected = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -26,16 +26,21 @@
|
|||
<i class="fa fa-angle-down" v-show="sortingByAlbum && order > 0"/>
|
||||
<i class="fa fa-angle-up" v-show="sortingByAlbum && order < 0"/>
|
||||
</th>
|
||||
<th @click="sort('fmtLength')" class="time">Time
|
||||
<i class="fa fa-angle-down" v-show="sortKey === 'fmtLength' && order > 0"/>
|
||||
<i class="fa fa-angle-up" v-show="sortKey === 'fmtLength' && order < 0"/>
|
||||
<th @click="sort('length')" class="time">Time
|
||||
<i class="fa fa-angle-down" v-show="sortKey === 'length' && order > 0"/>
|
||||
<i class="fa fa-angle-up" v-show="sortKey === 'length' && order < 0"/>
|
||||
</th>
|
||||
<th class="play"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr is="song-item" v-for="item in displayedItems" :song="item" ref="rows"/>
|
||||
<tr is="song-item"
|
||||
v-for="item in displayedItems"
|
||||
@itemClicked="itemClicked"
|
||||
:song="item"
|
||||
:key="item.id"
|
||||
ref="rows"/>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -69,7 +74,6 @@ export default {
|
|||
q: '', // The filter query
|
||||
sortKey: '',
|
||||
order: 1,
|
||||
componentCache: {},
|
||||
sortingByAlbum: false,
|
||||
sortingByArtist: false,
|
||||
selectedSongs: [],
|
||||
|
@ -117,7 +121,7 @@ export default {
|
|||
/**
|
||||
* Handle sorting the song list.
|
||||
*
|
||||
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'fmtLength'
|
||||
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'length'
|
||||
*/
|
||||
sort(key) {
|
||||
if (this.sortable === false) {
|
||||
|
@ -221,12 +225,7 @@ export default {
|
|||
* @return {Object} The Vue compoenent
|
||||
*/
|
||||
getComponentBySongId(id) {
|
||||
// A Vue component can be removed (as a result of filter for example), so we check for its $el as well.
|
||||
if (!this.componentCache[id] || !this.componentCache[id].$el) {
|
||||
this.componentCache[id] = find(this.$refs.rows, { song: { id } });
|
||||
}
|
||||
|
||||
return this.componentCache[id];
|
||||
return find(this.$refs.rows, { song: { id } });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -269,7 +268,7 @@ export default {
|
|||
* @param {String} songId
|
||||
* @param {Object} e
|
||||
*/
|
||||
rowClick(songId, e) {
|
||||
itemClicked(songId, e) {
|
||||
const row = this.getComponentBySongId(songId);
|
||||
|
||||
// If we're on a touch device, or if Ctrl/Cmd key is pressed, just toggle selection.
|
||||
|
@ -342,8 +341,11 @@ export default {
|
|||
this.gatherSelected();
|
||||
}
|
||||
|
||||
console.log('selected songs before drop:', this.selectedSongs);
|
||||
|
||||
this.$nextTick(() => {
|
||||
const songIds = map(this.selectedSongs, 'id');
|
||||
console.log('dragging', songIds);
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
|
@ -377,6 +379,7 @@ export default {
|
|||
* @param {Object} e
|
||||
*/
|
||||
handleDrop(songId, e) {
|
||||
console.log('dropping into', songId);
|
||||
if (this.type !== 'queue') {
|
||||
return this.removeDroppableState(e) && false;
|
||||
}
|
||||
|
@ -386,6 +389,7 @@ export default {
|
|||
}
|
||||
|
||||
const songs = this.selectedSongs;
|
||||
console.log('selected songs after drop:', songs);
|
||||
|
||||
if (!songs.length) {
|
||||
return this.removeDroppableState(e) && false;
|
||||
|
@ -458,12 +462,6 @@ export default {
|
|||
*/
|
||||
'main-content-view:load': () => this.clearSelection(),
|
||||
|
||||
/**
|
||||
* Listens to the 'song:selection-changed' dispatched from a child song-item
|
||||
* to collect the selected songs.
|
||||
*/
|
||||
'song:selection-changed': () => this.gatherSelected(),
|
||||
|
||||
/**
|
||||
* Listen to 'song:selection-clear' (often broadcasted from the direct parent)
|
||||
* to clear the selected songs.
|
||||
|
|
|
@ -2,12 +2,9 @@
|
|||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\Remote\RemoteWebElement;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
class KoelTest extends TestCase
|
||||
{
|
||||
|
@ -35,9 +32,8 @@ class KoelTest extends TestCase
|
|||
// Default URL must be Home
|
||||
static::assertEquals($this->url.'/#!/home', $this->driver->getCurrentURL());
|
||||
|
||||
// $this->_testSideBar();
|
||||
// $this->_testHomeScreen();
|
||||
return $this->_testQueueScreen();
|
||||
$this->_testSideBar();
|
||||
$this->_testHomeScreen();
|
||||
|
||||
// While we're at this, test logging out as well.
|
||||
$this->click('#userBadge > a.logout');
|
||||
|
@ -49,8 +45,8 @@ class KoelTest extends TestCase
|
|||
private function _testSideBar()
|
||||
{
|
||||
// All basic navigation
|
||||
foreach(['home', 'queue', 'songs', 'albums', 'artists', 'youtube', 'settings', 'users'] as $screen) {
|
||||
$this->goTo($screen);
|
||||
foreach (['home', 'queue', 'songs', 'albums', 'artists', 'youtube', 'settings', 'users'] as $screen) {
|
||||
$this->goto($screen);
|
||||
$this->waitUntil(function () use ($screen) {
|
||||
return $this->driver->getCurrentURL() === $this->url.'/#!/'.$screen;
|
||||
});
|
||||
|
@ -74,11 +70,8 @@ class KoelTest extends TestCase
|
|||
$this->click('#homeWrapper section.recently-added article:nth-child(1) span.right a:nth-child(1)');
|
||||
static::assertCount(10, $this->els('#queueWrapper .song-list-wrap tr.song-item'));
|
||||
|
||||
$this->goTo('home');
|
||||
$this->goto('home');
|
||||
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector('#homeWrapper section.recently-added')
|
||||
));
|
||||
// Simulate a "double click to play" action
|
||||
/** @var $clickedSong RemoteWebElement */
|
||||
$clickedSong = $this->el('#homeWrapper section.recently-added > div > div:nth-child(2) li:nth-child(1) .details');
|
||||
|
@ -88,87 +81,4 @@ class KoelTest extends TestCase
|
|||
$mostRecentSong = $this->el('#homeWrapper .recently-added-song-list .song-item-home:nth-child(1) .details');
|
||||
static::assertEquals($mostRecentSong->getText(), $clickedSong->getText());
|
||||
}
|
||||
|
||||
private function _testQueueScreen()
|
||||
{
|
||||
$this->goTo('queue');
|
||||
static::assertContains('Current Queue', $this->el('#queueWrapper > h1 > span')->getText());
|
||||
|
||||
// Clear the queue
|
||||
$this->clearQueue();
|
||||
static::assertEmpty($this->els('#queueWrapper tr.song-item'));
|
||||
|
||||
// Go back to Albums and queue an album of 10 songs
|
||||
$this->goTo('albums');
|
||||
$this->click('#albumsWrapper > div > article:nth-child(1) > footer > p > span.right > a:nth-child(1)');
|
||||
|
||||
$this->goTo('queue');
|
||||
// Single song selection
|
||||
static::assertContains(
|
||||
'selected',
|
||||
$this->click('#queueWrapper tr.song-item:nth-child(1)')->getAttribute('class')
|
||||
);
|
||||
// shift+click
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::SHIFT)
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(5)'))
|
||||
->keyUp(null, WebDriverKeys::SHIFT)
|
||||
->perform();
|
||||
// should have 5 selected rows
|
||||
static::assertCount(5, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
// Cmd+Click
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::COMMAND)
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(2)'))
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(3)'))
|
||||
->keyUp(null, WebDriverKeys::COMMAND)
|
||||
->perform();
|
||||
// should have only 3 selected rows remaining
|
||||
static::assertCount(3, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
// 2nd and 3rd rows must not be selected
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(2)')->getAttribute('class')
|
||||
);
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(3)')->getAttribute('class')
|
||||
);
|
||||
// Delete key should remove selected songs
|
||||
$this->press(WebDriverKeys::DELETE);
|
||||
$this->waitUntil(function () {
|
||||
return count($this->els('#queueWrapper tr.song-item.selected')) === 0
|
||||
&& count($this->els('#queueWrapper tr.song-item')) === 7;
|
||||
});
|
||||
|
||||
// Ctrl+A/Cmd+A should select all remaining songs
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::COMMAND)
|
||||
->keyDown(null, 'A')
|
||||
->keyUp(null, 'A')
|
||||
->keyUp(null, WebDriverKeys::COMMAND)
|
||||
->perform();
|
||||
static::assertCount(7, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
// Try adding these songs into a new playlist
|
||||
$this->click('#queueWrapper .buttons button.btn.btn-green');
|
||||
$this->typeIn('#queueWrapper .buttons input[type="text"]', 'Foo');
|
||||
$this->enter();
|
||||
$this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement(
|
||||
WebDriverBy::cssSelector('#playlists > ul'), 'Foo'
|
||||
));
|
||||
|
||||
// Now go back to the queue and try adding a song into favorites
|
||||
$this->goTo('queue');
|
||||
/** @var WebDriverElement $firstSongInQueue */
|
||||
$firstSongInQueue = $this->el('#queueWrapper tr.song-item:nth-child(1) .title')->click();
|
||||
// TODO: There's a bug here.
|
||||
// After deleting, selection goes wrong (clicking first row selects second).
|
||||
var_dump($firstSongInQueue->getText());
|
||||
$this->click('#queueWrapper .buttons button.btn.btn-green');
|
||||
$this->click('#queueWrapper .buttons li:nth-child(1)');
|
||||
$this->goTo('favorites');
|
||||
var_dump($this->el('#favoritesWrapper tr.song-item:nth-last-child(1) .title')->getText());
|
||||
static::assertEquals($firstSongInQueue->getText(),
|
||||
$this->el('#favoritesWrapper tr.song-item:nth-last-child(1) .title')->getText());
|
||||
}
|
||||
}
|
||||
|
|
24
tests/e2e/QueueScreenTest.php
Normal file
24
tests/e2e/QueueScreenTest.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
class QueueScreenTest extends TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->loginAndWait();
|
||||
$this->goto('queue');
|
||||
static::assertContains('Current Queue', $this->el('#queueWrapper > h1 > span')->getText());
|
||||
|
||||
// As the queue is currently empty, the "Shuffling all song" link should be there
|
||||
$this->click('#queueWrapper a.start');
|
||||
$this->waitUntil(function () {
|
||||
return count($this->els('#queueWrapper .song-item'));
|
||||
});
|
||||
|
||||
// Clear the queue
|
||||
$this->click('#queueWrapper .buttons button.btn.btn-red');
|
||||
static::assertEmpty($this->els('#queueWrapper tr.song-item'));
|
||||
}
|
||||
|
||||
}
|
195
tests/e2e/SongListTest.php
Normal file
195
tests/e2e/SongListTest.php
Normal file
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
class SongListTest extends TestCase
|
||||
{
|
||||
public function testSelection()
|
||||
{
|
||||
$this->loginAndWait()->repopulateList();
|
||||
|
||||
// Single song selection
|
||||
static::assertContains(
|
||||
'selected',
|
||||
$this->click('#queueWrapper tr.song-item:nth-child(1)')->getAttribute('class')
|
||||
);
|
||||
|
||||
// shift+click
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::SHIFT)
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(5)'))
|
||||
->keyUp(null, WebDriverKeys::SHIFT)
|
||||
->perform();
|
||||
// should have 5 selected rows
|
||||
static::assertCount(5, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
|
||||
// Cmd+Click
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::COMMAND)
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(2)'))
|
||||
->click($this->el('#queueWrapper tr.song-item:nth-child(3)'))
|
||||
->keyUp(null, WebDriverKeys::COMMAND)
|
||||
->perform();
|
||||
// should have only 3 selected rows remaining
|
||||
static::assertCount(3, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
// 2nd and 3rd rows must not be selected
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(2)')->getAttribute('class')
|
||||
);
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(3)')->getAttribute('class')
|
||||
);
|
||||
|
||||
// Delete key should remove selected songs
|
||||
$this->press(WebDriverKeys::DELETE);
|
||||
$this->waitUntil(function () {
|
||||
return count($this->els('#queueWrapper tr.song-item.selected')) === 0
|
||||
&& count($this->els('#queueWrapper tr.song-item')) === 7;
|
||||
});
|
||||
|
||||
// Ctrl+A/Cmd+A should select all songs
|
||||
$this->selectAll();
|
||||
static::assertCount(7, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
}
|
||||
|
||||
public function testActionButtons()
|
||||
{
|
||||
$this->loginAndWait()->repopulateList();
|
||||
|
||||
// Since no songs are selected, the shuffle button must read "(Shuffle) All"
|
||||
static::assertContains('ALL', $this->el('#queueWrapper button.play-shuffle')->getText());
|
||||
// Now we selected all songs it to read "(Shuffle) Selected"
|
||||
$this->selectAll();
|
||||
static::assertContains('SELECTED', $this->el('#queueWrapper button.play-shuffle')->getText());
|
||||
|
||||
// Add to favorites
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(1)')->click();
|
||||
$this->click('#queueWrapper .buttons button.btn.btn-green');
|
||||
$this->click('#queueWrapper .buttons li:nth-child(1)');
|
||||
$this->goto('favorites');
|
||||
static::assertCount(1, $this->els('#favoritesWrapper tr.song-item'));
|
||||
|
||||
$this->goto('queue');
|
||||
$this->click('#queueWrapper tr.song-item:nth-child(1)');
|
||||
// Try adding a song into a new playlist
|
||||
$this->click('#queueWrapper .buttons button.btn.btn-green');
|
||||
$this->typeIn('#queueWrapper .buttons input[type="text"]', 'Foo');
|
||||
$this->enter();
|
||||
$this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement(
|
||||
WebDriverBy::cssSelector('#playlists > ul'), 'Foo'
|
||||
));
|
||||
}
|
||||
|
||||
public function testSorting()
|
||||
{
|
||||
$this->loginAndWait()->repopulateList();
|
||||
|
||||
// Confirm that we can't sort in Queue screen
|
||||
/** @var WebDriverElement $th */
|
||||
foreach ($this->els('#queueWrapper div.song-list-wrap th') as $th) {
|
||||
if (!$th->isDisplayed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($th->findElements(WebDriverBy::tagName('i')) as $sortDirectionIcon) {
|
||||
static::assertFalse($sortDirectionIcon->isDisplayed());
|
||||
}
|
||||
}
|
||||
|
||||
// Now go to All Songs screen and sort there
|
||||
$this->goto('songs');
|
||||
$this->click('#songsWrapper div.song-list-wrap th:nth-child(2)');
|
||||
$last = null;
|
||||
$results = [];
|
||||
/** @var WebDriverElement $td */
|
||||
foreach ($this->els('#songsWrapper div.song-list-wrap td.title') as $td) {
|
||||
$current = $td->getText();
|
||||
$results[] = $last === null ? true : $current <= $last;
|
||||
$last = $current;
|
||||
|
||||
}
|
||||
static::assertNotContains(false, $results);
|
||||
|
||||
// Second click will reverse the sort
|
||||
$this->click('#songsWrapper div.song-list-wrap th:nth-child(2)');
|
||||
$last = null;
|
||||
$results = [];
|
||||
/** @var WebDriverElement $td */
|
||||
foreach ($this->els('#songsWrapper div.song-list-wrap td.title') as $td) {
|
||||
$current = $td->getText();
|
||||
$results[] = $last === null ? true : $current >= $last;
|
||||
$last = $current;
|
||||
}
|
||||
static::assertNotContains(false, $results);
|
||||
}
|
||||
|
||||
public function testContextMenu()
|
||||
{
|
||||
$this->loginAndWait()->goto('songs');
|
||||
$this->rightClick('#songsWrapper tr.song-item:nth-child(1)');
|
||||
|
||||
$by = WebDriverBy::cssSelector('#songsWrapper .song-menu');
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated($by));
|
||||
|
||||
// 7 sub menu items
|
||||
static::assertCount(7, $this->els('#songsWrapper .song-menu > li'));
|
||||
|
||||
// Clicking the "Go to Album" menu item
|
||||
$this->click('#songsWrapper .song-menu > li:nth-child(2)');
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector('#albumWrapper')
|
||||
));
|
||||
|
||||
// Clicking the "Go to Artist" menu item
|
||||
$this->back();
|
||||
$this->rightClick('#songsWrapper tr.song-item:nth-child(1)');
|
||||
$this->click('#songsWrapper .song-menu > li:nth-child(3)');
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector('#artistWrapper')
|
||||
));
|
||||
|
||||
// Clicking "Edit"
|
||||
$this->back();
|
||||
$this->rightClick('#songsWrapper tr.song-item:nth-child(1)');
|
||||
$this->click('#songsWrapper .song-menu > li:nth-child(5)');
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector('#editSongsOverlay form')
|
||||
));
|
||||
// Updating song
|
||||
$this->typeIn('#editSongsOverlay form input[name="title"]', 'Foo');
|
||||
$this->typeIn('#editSongsOverlay form input[name="track"]', 99);
|
||||
$this->enter();
|
||||
$this->waitUntil(WebDriverExpectedCondition::invisibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector('#editSongsOverlay form')
|
||||
));
|
||||
static::assertEquals('99', $this->el('#songsWrapper tr.song-item:nth-child(1) .track-number')->getText());
|
||||
static::assertEquals('Foo', $this->el('#songsWrapper tr.song-item:nth-child(1) .title')->getText());
|
||||
}
|
||||
|
||||
private function repopulateList()
|
||||
{
|
||||
// Go back to Albums and queue an album of 10 songs
|
||||
$this->goto('albums');
|
||||
$this->click('#albumsWrapper > div > article:nth-child(1) > footer > p > span.right > a:nth-child(1)');
|
||||
$this->goto('queue');
|
||||
}
|
||||
|
||||
private function selectAll()
|
||||
{
|
||||
$this->focusIntoApp(); // make sure focus is there before executing shortcut keys
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::COMMAND)
|
||||
->keyDown(null, 'A')
|
||||
->keyUp(null, 'A')
|
||||
->keyUp(null, WebDriverKeys::COMMAND)
|
||||
->perform();
|
||||
}
|
||||
}
|
|
@ -3,22 +3,16 @@
|
|||
namespace E2E;
|
||||
|
||||
use App\Application;
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverDoubleClickAction;
|
||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverDimension;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
use Facebook\WebDriver\WebDriverPoint;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class TestCase extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var RemoteWebDriver
|
||||
*/
|
||||
protected $driver;
|
||||
use WebDriverShortcuts;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
|
@ -42,8 +36,6 @@ class TestCase extends \PHPUnit_Framework_TestCase
|
|||
$this->createApp();
|
||||
$this->prepareForE2E();
|
||||
$this->driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());
|
||||
$this->driver->manage()->window()->setPosition(new WebDriverPoint(0, 0))
|
||||
->setSize(new WebDriverDimension(1440, 900));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,85 +64,6 @@ class TestCase extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->driver->get($this->url);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
//$this->driver->quit();
|
||||
}
|
||||
|
||||
protected function el($selector)
|
||||
{
|
||||
return $this->driver->findElement(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
protected function els($selector)
|
||||
{
|
||||
return $this->driver->findElements(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
protected function type($string)
|
||||
{
|
||||
return $this->driver->getKeyboard()->sendKeys($string);
|
||||
}
|
||||
|
||||
protected function typeIn($element, $string)
|
||||
{
|
||||
if (is_string($element)) {
|
||||
$element = $this->el($element);
|
||||
}
|
||||
$element->click()->clear();
|
||||
|
||||
return $this->type($string);
|
||||
}
|
||||
|
||||
protected function press($key = WebDriverKeys::ENTER)
|
||||
{
|
||||
return $this->driver->getKeyboard()->pressKey($key);
|
||||
}
|
||||
|
||||
protected function enter()
|
||||
{
|
||||
return $this->press();
|
||||
}
|
||||
|
||||
protected function click($element)
|
||||
{
|
||||
return $this->el($element)->click();
|
||||
}
|
||||
|
||||
protected function doubleClick($element)
|
||||
{
|
||||
if (is_string($element)) {
|
||||
$element = $this->el($element);
|
||||
}
|
||||
$action = new WebDriverDoubleClickAction($this->driver->getMouse(), $element);
|
||||
|
||||
return $action->perform();
|
||||
}
|
||||
|
||||
protected function sleep($seconds)
|
||||
{
|
||||
$this->driver->manage()->timeouts()->implicitlyWait($seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a condition is met.
|
||||
*
|
||||
* @param $func (closure|WebDriverExpectedCondition)
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function waitUntil($func, $timeout = 10)
|
||||
{
|
||||
return $this->driver->wait($timeout)->until($func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log into Koel.
|
||||
*
|
||||
|
@ -164,27 +77,57 @@ class TestCase extends \PHPUnit_Framework_TestCase
|
|||
$this->enter();
|
||||
}
|
||||
|
||||
protected function loginAndWait()
|
||||
{
|
||||
$this->login();
|
||||
$this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement(
|
||||
WebDriverBy::cssSelector('#userBadge > a.view-profile.control > span'), 'Koel Admin'
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to allow going to a specific screen.
|
||||
*
|
||||
* @param $screen
|
||||
*
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function goTo($screen)
|
||||
protected function goto($screen)
|
||||
{
|
||||
if ($screen === 'favorites') {
|
||||
return $this->click('#sidebar .favorites a');
|
||||
$this->click('#sidebar .favorites a');
|
||||
} else {
|
||||
return $this->click("#sidebar a.$screen");
|
||||
$this->click("#sidebar a.$screen");
|
||||
}
|
||||
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector("#{$screen}Wrapper")
|
||||
));
|
||||
}
|
||||
|
||||
protected function waitForUserInput()
|
||||
{
|
||||
if (trim(fgets(fopen('php://stdin', 'rb'))) !== chr(13)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function clearQueue()
|
||||
protected function focusIntoApp()
|
||||
{
|
||||
$this->goTo('queue');
|
||||
if ($this->els('#queueWrapper .song-item')) {
|
||||
$this->click('#queueWrapper > h1 > div > button.btn.btn-red');
|
||||
}
|
||||
$this->click('#app');
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->driver->get($this->url);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->driver->quit();
|
||||
}
|
||||
}
|
||||
|
|
114
tests/e2e/WebDriverShortcuts.php
Normal file
114
tests/e2e/WebDriverShortcuts.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverDoubleClickAction;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
trait WebDriverShortcuts
|
||||
{
|
||||
/**
|
||||
* @var RemoteWebDriver
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* @param $selector WebDriverElement|string
|
||||
*
|
||||
* @return WebDriverElement
|
||||
*/
|
||||
protected function el($selector)
|
||||
{
|
||||
if (is_a($selector, WebDriverElement::class)) {
|
||||
return $selector;
|
||||
}
|
||||
|
||||
return $this->driver->findElement(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
protected function els($selector)
|
||||
{
|
||||
return $this->driver->findElements(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
protected function type($string)
|
||||
{
|
||||
return $this->driver->getKeyboard()->sendKeys($string);
|
||||
}
|
||||
|
||||
protected function typeIn($element, $string)
|
||||
{
|
||||
$this->click($element)->clear();
|
||||
|
||||
return $this->type($string);
|
||||
}
|
||||
|
||||
protected function press($key = WebDriverKeys::ENTER)
|
||||
{
|
||||
return $this->driver->getKeyboard()->pressKey($key);
|
||||
}
|
||||
|
||||
protected function enter()
|
||||
{
|
||||
return $this->press();
|
||||
}
|
||||
|
||||
protected function click($element)
|
||||
{
|
||||
return $this->el($element)->click();
|
||||
}
|
||||
|
||||
protected function rightClick($element)
|
||||
{
|
||||
return $this->driver->getMouse()->contextClick($this->el($element)->getCoordinates());
|
||||
}
|
||||
|
||||
protected function doubleClick($element)
|
||||
{
|
||||
$action = new WebDriverDoubleClickAction($this->driver->getMouse(), $this->el($element));
|
||||
|
||||
$action->perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep (implicit wait) for some seconds.
|
||||
*
|
||||
* @param $seconds
|
||||
*/
|
||||
protected function sleep($seconds)
|
||||
{
|
||||
$this->driver->manage()->timeouts()->implicitlyWait($seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a condition is met.
|
||||
*
|
||||
* @param $func (closure|WebDriverExpectedCondition)
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function waitUntil($func, $timeout = 10)
|
||||
{
|
||||
return $this->driver->wait($timeout)->until($func);
|
||||
}
|
||||
|
||||
protected function back()
|
||||
{
|
||||
return $this->driver->navigate()->back();
|
||||
}
|
||||
|
||||
protected function forward()
|
||||
{
|
||||
return $this->driver->navigate()->forward();
|
||||
}
|
||||
|
||||
protected function refresh()
|
||||
{
|
||||
return $this->driver->navigate()->refresh();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue