Merge pull request #9982 from nextcloud/global-flow-rebuild

Cleanup structure
This commit is contained in:
Morris Jobke 2018-07-20 23:40:00 +02:00 committed by GitHub
commit 53aaa62572
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 7874 additions and 452 deletions

View File

@ -121,7 +121,8 @@
position: absolute;
top: 0;
right: 0;
padding: 14px;
opacity: .5;
z-index: 1;
width: 44px;
height: 44px;
}

View File

@ -49,6 +49,7 @@
#filestable {
position: relative;
width: 100%;
min-width: 500px;
}
#filestable tbody tr {

View File

@ -9,7 +9,7 @@
/* dont require a minimum width for files table */
#body-user #filestable {
min-width: initial !important;
min-width: 300px;
}
table th#headerSize,

View File

@ -117,6 +117,13 @@
* Renders this details view
*/
render: function() {
// remove old instances
if ($('#app-sidebar').length === 0) {
this.$el.insertAfter($('#app-content'));
} else {
$('#app-sidebar').replaceWith(this.$el)
}
var templateVars = {
closeLabel: t('files', 'Close')
};

View File

@ -99,11 +99,12 @@
/**
* Number of files per page
* Always show a minimum of 1
*
* @return {int} page size
*/
pageSize: function() {
return Math.ceil(this.$container.height() / 50);
return Math.max(Math.ceil(this.$container.height() / 50), 1);
},
/**
@ -274,7 +275,6 @@
if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) {
this._detailsView = new OCA.Files.DetailsView();
this._detailsView.$el.insertBefore(this.$el);
this._detailsView.$el.addClass('disappear');
}

View File

@ -14,6 +14,7 @@ describe('OCA.Files.FavoritesPlugin tests', function() {
beforeEach(function() {
$('#testArea').append(
'<div id="content">' +
'<div id="app-navigation">' +
'<ul><li data-id="files"><a>Files</a></li>' +
'<li data-id="sharingin"><a></a></li>' +
@ -25,6 +26,7 @@ describe('OCA.Files.FavoritesPlugin tests', function() {
'<div id="app-content-favorites" class="hidden">' +
'</div>' +
'</div>' +
'</div>' +
'</div>'
);
OC.Plugins.attach('OCA.Files.App', Plugin);

View File

@ -2491,7 +2491,8 @@ describe('OCA.Files.FileList tests', function() {
expect($('#app-sidebar').hasClass('disappear')).toEqual(false);
fileList.remove('One.txt');
expect($('#app-sidebar').hasClass('disappear')).toEqual(true);
// sidebar is removed on close before being
expect($('#app-sidebar').length).toEqual(0);
jQuery.fx.off = false;
});
it('returns the currently selected model instance when calling getModelForFile', function() {
@ -2515,7 +2516,7 @@ describe('OCA.Files.FileList tests', function() {
expect($('#app-sidebar').hasClass('disappear')).toEqual(false);
fileList.changeDirectory('/another');
expect($('#app-sidebar').hasClass('disappear')).toEqual(true);
expect($('#app-sidebar').length).toEqual(0);
jQuery.fx.off = false;
});
});

View File

@ -67,7 +67,7 @@ OCA.Sharing.PublicApp = {
$el,
{
id: 'files.public',
scrollContainer: $('#content-wrapper'),
scrollContainer: $('#app-content'),
dragOptions: dragOptions,
folderDropOptions: folderDropOptions,
fileActions: fileActions,

7453
build/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,13 @@
"jsdoc": "~3.5.5",
"karma": "^2.0.2",
"karma-coverage": "*",
"karma-jasmine": "^1.1.0",
"karma-jasmine": "^1.1.2",
"karma-jasmine-sinon": "^1.0.4",
"karma-junit-reporter": "*",
"karma-phantomjs-launcher": "*",
"karma-junit-reporter": "^1.2.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-viewport": "^1.0.2",
"phantomjs-prebuilt": "*",
"node-sass": "~4.9.0",
"phantomjs-prebuilt": "*",
"sinon": "<= 5.0.7"
},
"engine": "node >= 6.9"

View File

@ -16,7 +16,7 @@
*
*/
/* BASE STYLING ---------------------------------------------------------- */
/* BASE STYLING ------------------------------------------------------------ */
h2 {
font-size: 20px;
@ -64,24 +64,16 @@ kbd {
}
/* APP STYLING -------------------------------------------------------------- */
/* APP STYLING ------------------------------------------------------------ */
#app {
height: 100%;
width: 100%;
* {
box-sizing: border-box;
}
#content[class*='app-'] * {
box-sizing: border-box;
}
/* APP-NAVIGATION ------------------------------------------------------------*/
/* APP-NAVIGATION ------------------------------------------------------------ */
/* Navigation: folder like structure */
#app-navigation {
width: 250px;
height: 100%;
float: left;
width: $navigation-width;
box-sizing: border-box;
background-color: var(--color-main-background);
-webkit-user-select: none;
@ -91,6 +83,8 @@ kbd {
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
flex-grow: 0;
flex-shrink: 0;
/* 'New' button */
.app-navigation-new {
@ -580,57 +574,85 @@ kbd {
}
}
/* APP-CONTENT ---------------------------------------------------------------*/
/* CONTENT --------------------------------------------------------- */
#content {
/* header height */
padding-top: $header-height;
box-sizing: border-box;
position: relative;
overflow-x: hidden;
display: flex;
/* trick: scroll #app-content and not the body
* to avoid double scrollbar with sidebar
*/
max-height: 100vh;
}
/* APP-CONTENT AND WRAPPER ------------------------------------------ */
/* Part where the content will be loaded into */
#app-content {
z-index: 1000;
background-color: var(--color-main-background);
position: relative;
height: 100%;
overflow-y: auto;
min-height: calc(100vh - #{$header-height});
flex-basis: 100vw;
overflow: auto;
/* no top border for first settings item */
> .section:first-child {
border-top: none;
}
&.with-app-sidebar {
margin-right: 27%;
/* if app-content-list is present */
#app-content-wrapper {
display: flex;
position: relative;
align-items: start;
.app-content-list,
.app-content-detail {
min-height: calc(100vh - #{$header-height});
max-height: calc(100vh - #{$header-height});
overflow-x: hidden;
overflow-y: auto;
}
/* CONTENT DETAILS AFTER LIST*/
.app-content-detail {
/* grow full width */
flex-grow: 1;
#app-navigation-toggle-back {
display: none;
}
}
}
}
#app-content-wrapper {
min-width: 100%;
min-height: 100%;
}
/* APP-SIDEBAR ----------------------------------------------------------------*/
/* APP-SIDEBAR ------------------------------------------------------------ */
/*
Sidebar: a sidebar to be used within #app-content
have it as first element within app-content in order to shrink other
sibling containers properly. Compare Files app for example.
Sidebar: a sidebar to be used within #content
#app-content will be shrinked properly
*/
#app-sidebar {
position: fixed;
top: 50px;
right: 0;
left: auto;
bottom: 0;
width: 27%;
min-width: 300px;
width: 27vw;
min-width: $sidebar-min-width;
max-width: $sidebar-max-width;
display: block;
position: relative;
background: var(--color-main-background);
border-left: 1px solid var(--color-border);
-webkit-transition: margin-right 300ms;
transition: margin-right 300ms;
overflow-x: hidden;
overflow-y: auto;
visibility: visible;
z-index: 500;
flex-shrink: 0;
transition: 300ms width ease-in-out,
300ms min-width ease-in-out;
&.disappear {
visibility: hidden;
width: 0;
min-width: 0;
}
}
/* APP-SETTINGS ---------------------------------------------------------------*/
/* APP-SETTINGS ------------------------------------------------------------ */
/* settings area */
#app-settings {
// To the bottom w/ flex
@ -650,8 +672,6 @@ kbd {
/* restrict height of settings and make scrollable */
max-height: 300px;
overflow-y: auto;
border-right: 1px solid var(--color-border);
width: 250px;
box-sizing: border-box;
/* display input fields at full width */
@ -679,8 +699,6 @@ kbd {
}
#app-settings-header {
border-right: 1px solid var(--color-border);
width: 250px;
box-sizing: border-box;
background-color: var(--color-main-background);
}
@ -716,7 +734,7 @@ kbd {
}
}
/* GENERAL SECTION ---------------------------------------------------------- */
/* GENERAL SECTION ------------------------------------------------------------ */
.section {
display: block;
padding: 30px;
@ -753,7 +771,7 @@ kbd {
}
}
/* TABS --------------------------------------------------------------------- */
/* TABS ------------------------------------------------------------ */
.tabHeaders {
display: inline-block;
margin: 15px;
@ -786,7 +804,7 @@ kbd {
}
}
/* POPOVER MENU ------------------------------------------------------------- */
/* POPOVER MENU ------------------------------------------------------------ */
$popoveritem-height: 38px;
$popovericon-size: 16px;
@ -1007,23 +1025,7 @@ $popovericon-size: 16px;
}
}
/* CONTENT WRAPPER --------------------------------------------------------- */
#app-content-wrapper {
display: flex;
position: relative;
align-items: start;
height: 100%;
width: 100%;
.app-content-list,
.app-content-detail {
min-height: 100%;
max-height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
}
/* CONTENT LIST ------------------------------------------------------------- */
/* CONTENT LIST ------------------------------------------------------------ */
.app-content-list {
width: 300px;
border-right: 1px solid var(--color-border);
@ -1175,62 +1177,3 @@ $popovericon-size: 16px;
}
}
}
/* CONTENT ------------------------------------------------------------------ */
.app-content-detail {
/* grow full width */
flex-grow: 1;
#app-navigation-toggle-back {
display: none;
}
}
/* MOBILE ------------------------------------------------------------------- */
/* Mobile width < 768px */
@media only screen and (max-width: 768px) {
/* full width for message list on mobile */
.app-content-list {
width: 100%;
background: var(--color-main-background);
position: relative;
z-index: 100;
}
/* overlay message detail on top of message list */
.app-content-detail {
background: var(--color-main-background);
width: 100%;
left: 0;
height: 100%;
top: 0;
box-shadow: 0 0 100px rgba(100, 100, 100, .9);
position: absolute;
}
/* Show app details page */
#app-content.showdetails {
#app-navigation-toggle {
transform: translateX(-44px);
}
#app-navigation-toggle-back {
position: fixed;
display: inline-block !important;
top: 45px;
left: 0;
width: 44px;
height: 44px;
z-index: 149;
background-color: rgba(255, 255, 255, .7);
cursor: pointer;
opacity: .6;
transform: rotate(90deg);
}
.app-content-list {
transform: translateX(-100%);
}
}
/* end of media query */
}

View File

@ -49,7 +49,7 @@
left: 0;
right: 0;
z-index: 2000;
height: 50px;
height: $header-height;
background-color: var(--color-primary);
box-sizing: border-box;
justify-content: space-between;
@ -81,7 +81,7 @@
max-width: 350px;
max-height: 280px;
right: 5px;
top: 50px;
top: $header-height;
margin: 0;
&:not(.popovermenu) {
@ -165,7 +165,7 @@
display: flex;
justify-content: center;
align-items: center;
width: 50px;
width: $header-height;
height: 100%;
cursor: pointer;
opacity: 0.6;
@ -224,9 +224,9 @@
/* NAVIGATION --------------------------------------------------------------- */
nav[role='navigation'] {
display: inline-block;
width: 50px;
height: 50px;
margin-left: -50px;
width: $header-height;
height: $header-height;
margin-left: -$header-height;
}
.header-left #navigation {
@ -439,28 +439,21 @@ nav[role='navigation'] {
/* Apps menu */
#appmenu {
display: inline-block;
width: auto;
min-width: 50px;
height: 100%;
clear: both;
display: inline-flex;
min-width: $header-height;
li {
float: left;
display: inline-block;
position: relative;
vertical-align: top !important;
height: 100%;
cursor: pointer;
a {
position: relative;
display: inline-block;
display: flex;
margin: 0;
padding: 15px 15px;
height: 20px;
text-align: center;
vertical-align: top !important;
height: $header-height;
width: $header-height;
align-items: center;
justify-content: center;
opacity: .6;
}
}
@ -582,7 +575,7 @@ nav[role='navigation'] {
&:focus,
&:active {
top: 50px;
top: $header-height;
}
}

View File

@ -1,124 +1,147 @@
@media only screen and (max-width: 768px) {
/* do not show update notification on mobile */
#update-notification {
display: none !important;
/* position share dropdown */
#dropdown {
margin-right: 10% !important;
width: 80% !important;
}
/* fix name autocomplete not showing on mobile */
.ui-autocomplete {
z-index: 1000 !important;
}
/* fix error display on smaller screens */
.error-wide {
width: 100%;
margin-left: 0 !important;
box-sizing: border-box;
}
/* APP SIDEBAR TOGGLE and SWIPE ----------------------------------------------*/
#app-navigation {
transform: translateX(-250px);
}
.snapjs-left {
#app-navigation {
transform: translateX(0);
}
}
#app-content {
margin-left: -$navigation-width;
}
/* full width for message list on mobile */
.app-content-list {
width: 100%;
background: var(--color-main-background);
position: relative;
z-index: 100;
}
/* since list and content are only displayed full window size
* we don't ant inner scrolling
*/
#app-content-wrapper {
.app-content-list,
.app-content-detail {
max-height: unset;
}
}
/* Show app details page */
#app-content.showdetails {
#app-navigation-toggle {
transform: translateX(-44px);
}
#app-navigation-toggle-back {
position: fixed;
display: inline-block !important;
top: 45px;
left: 0;
width: 44px;
height: 44px;
z-index: 149;
background-color: rgba(255, 255, 255, .7);
cursor: pointer;
opacity: .6;
transform: rotate(90deg);
}
.app-content-list {
transform: translateX(-100%);
}
/* end of media query */
}
/* position share dropdown */
#dropdown {
margin-right: 10% !important;
width: 80% !important;
}
/* fix name autocomplete not showing on mobile */
.ui-autocomplete {
z-index: 1000 !important;
}
/* fix error display on smaller screens */
.error-wide {
width: 100%;
margin-left: 0 !important;
box-sizing: border-box;
}
/* APP SIDEBAR TOGGLE and SWIPE ----------------------------------------------*/
#app-navigation,
#app-content {
position: absolute !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#app-navigation {
width: 250px !important;
}
#app-content {
width: 100% !important;
left: 0 !important;
background-color: var(--color-main-background);
overflow-x: hidden !important;
z-index: 1000;
}
/* allow horizontal scrollbar in settings
/* allow horizontal scrollbar in settings
otherwise user management is not usable on mobile */
#body-settings #app-content {
overflow-x: auto !important;
}
#body-settings #app-content {
overflow-x: auto !important;
}
#app-navigation-toggle {
position: fixed;
display: inline-block !important;
top: 50px;
left: 0;
width: 44px;
height: 44px;
z-index: 149;
background-color: var(--color-main-background-darker);
cursor: pointer;
opacity: .6;
}
#app-navigation-toggle:hover,
#app-navigation-toggle:focus {
opacity: 1;
}
#app-navigation-toggle {
position: fixed;
display: inline-block !important;
top: $header-height;
left: 0;
width: 44px;
height: 44px;
z-index: 149;
background-color: var(--color-main-background-darker);
cursor: pointer;
opacity: 0.6;
}
#app-navigation-toggle:hover,
#app-navigation-toggle:focus {
opacity: 1;
}
/* position controls for apps with app-navigation */
#app-navigation+#app-content #controls {
padding-left: 44px;
}
/* position controls for apps with app-navigation */
#app-navigation + #app-content #controls {
padding-left: 44px;
}
/* .viewer-mode is when text editor, PDF viewer, etc is open */
#body-user .app-files.viewer-mode #controls {
padding-left: 0 !important;
}
.app-files.viewer-mode #app-navigation-toggle {
display: none !important;
}
/* .viewer-mode is when text editor, PDF viewer, etc is open */
#body-user .app-files.viewer-mode #controls {
padding-left: 0 !important;
}
.app-files.viewer-mode #app-navigation-toggle {
display: none !important;
}
table.multiselect thead {
left: 0 !important;
}
table.multiselect thead {
left: 0 !important;
}
/* prevent overflow in user management controls bar */
#usersearchform {
display: none;
}
#body-settings #controls {
min-width: 768px !important;
}
/* prevent overflow in user management controls bar */
#usersearchform {
display: none;
}
#body-settings #controls {
min-width: 768px !important;
}
/* do not show dates in filepicker */
#oc-dialog-filepicker-content .filelist .filesize,
#oc-dialog-filepicker-content .filelist .date {
display: none;
}
#oc-dialog-filepicker-content .filelist .filename {
max-width: 80%;
}
/* fix controls bar jumping when navigation is slid out */
.snapjs-left #app-navigation-toggle,
.snapjs-left #controls {
top: 0;
}
.snapjs-left table.multiselect thead {
top: 44px;
}
/* do not show dates in filepicker */
#oc-dialog-filepicker-content .filelist .filesize,
#oc-dialog-filepicker-content .filelist .date {
display: none;
}
#oc-dialog-filepicker-content .filelist .filename {
max-width: 80%;
}
/* fix controls bar jumping when navigation is slid out */
.snapjs-left #app-navigation-toggle,
.snapjs-left #controls {
top: 0;
}
.snapjs-left table.multiselect thead {
top: 44px;
}
/* end of media query */
/* end of media query */
}
@media only screen and (max-width: 480px) {
@ -131,7 +154,7 @@ table.multiselect thead {
}
/* Arrow directly child of menutoggle */
#header .header-right > div {
&.openedMenu{
&.openedMenu {
&::after {
display: block;
}

View File

@ -207,25 +207,6 @@ body {
}
}
#content {
position: relative;
height: 100%;
width: 100%;
.hascontrols {
margin-top: 45px;
}
}
#content-wrapper {
position: absolute;
height: 100%;
width: 100%;
overflow-x: hidden;
/* prevent horizontal scrollbar */
padding-top: 50px;
box-sizing: border-box;
}
/* allow horizontal scrollbar for personal and admin settings */
#body-settings:not(.snapjs-left) .app-settings {
@ -838,7 +819,7 @@ span.ui-icon {
}
.content {
max-height: calc(100% - 50px);
max-height: calc(100% - $header-height);
height: 100%;
overflow-y: auto;

View File

@ -75,3 +75,10 @@ $color-border-dark: nc-darken($color-main-background, 14%) !default;
$border-radius: 3px !default;
$font-face: 'Open Sans', Frutiger, Calibri, 'Myriad Pro', Myriad, sans-serif !default;
// various structure data
$header-height: 50px;
$navigation-width: 300px;
$sidebar-min-width: 300px;
$sidebar-max-width: 500px;

View File

@ -27,9 +27,8 @@
*/
exports.Apps.showAppSidebar = function($el) {
var $appSidebar = $el || $('#app-sidebar');
$appSidebar.removeClass('disappear')
.show('slide', { direction: 'right' }, 200);
$('#app-content').addClass('with-app-sidebar', 200).trigger(new $.Event('appresized'));
$appSidebar.removeClass('disappear');
$('#content').addClass('with-app-sidebar').trigger(new $.Event('appresized'));
};
/**
@ -40,11 +39,8 @@
*/
exports.Apps.hideAppSidebar = function($el) {
var $appSidebar = $el || $('#app-sidebar');
$appSidebar.hide('slide', { direction: 'right' }, 100,
function() {
$appSidebar.addClass('disappear');
});
$('#app-content').removeClass('with-app-sidebar', 100).trigger(new $.Event('appresized'));
$appSidebar.addClass('disappear');
$('#content').removeClass('with-app-sidebar').trigger(new $.Event('appresized'));
};
/**

View File

@ -22,7 +22,7 @@
describe('Apps base tests', function() {
describe('Sidebar utility functions', function() {
beforeEach(function() {
$('#testArea').append('<div id="app-content">Content</div><div id="app-sidebar">The sidebar</div>');
$('#testArea').append('<div id="content"><div id="app-content">Content</div><div id="app-sidebar">The sidebar</div></div>');
jQuery.fx.off = true;
});
afterEach(function() {
@ -41,7 +41,7 @@ describe('Apps base tests', function() {
});
it('triggers appresize event when visibility changed', function() {
var eventStub = sinon.stub();
$('#app-content').on('appresized', eventStub);
$('#content').on('appresized', eventStub);
OC.Apps.showAppSidebar();
expect(eventStub.calledOnce).toEqual(true);
OC.Apps.hideAppSidebar();

View File

@ -27,8 +27,7 @@
</head>
<body id="<?php p($_['bodyid']);?>">
<?php include('layout.noscript.warning.php'); ?>
<header>
<div id="header" class="<?php p($_['header-classes']); ?>">
<header id="header" class="<?php p($_['header-classes']); ?>">
<div class="header-left">
<span id="nextcloud">
<div class="logo logo-icon svg"></div>
@ -70,9 +69,7 @@
<?php } ?>
</div>
<?php } ?>
</div>
</header>
<div id="content-wrapper">
</header>
<div id="content" class="app-<?php p($_['appid']) ?>" role="main">
<?php print_unescaped($_['content']); ?>
</div>
@ -81,7 +78,6 @@
<p class="info"><?php print_unescaped($theme->getLongFooter()); ?></p>
</footer>
<?php } ?>
</div>
</body>
</html>

View File

@ -28,13 +28,13 @@
<body id="<?php p($_['bodyid']);?>">
<?php include 'layout.noscript.warning.php'; ?>
<a href="#app-content" class="button primary skip-navigation skip-content"><?php p($l->t('Skip to main content')); ?></a>
<a href="#app-navigation" class="button primary skip-navigation"><?php p($l->t('Skip to navigation of app')); ?></a>
<a href="#app-content" class="button primary skip-navigation skip-content"><?php p($l->t('Skip to main content')); ?></a>
<a href="#app-navigation" class="button primary skip-navigation"><?php p($l->t('Skip to navigation of app')); ?></a>
<div id="notification-container">
<div id="notification"></div>
</div>
<header role="banner"><div id="header">
<div id="notification-container">
<div id="notification"></div>
</div>
<header role="banner" id="header">
<div class="header-left">
<a href="<?php print_unescaped(link_to('', 'index.php')); ?>"
id="nextcloud">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,13 +21,13 @@
-->
<template>
<div id="app">
<div id="content" class="app-settings" :class="{ 'with-app-sidebar': currentApp}">
<app-navigation :menu="menu" />
<div id="app-content" class="app-settings-content" :class="{ 'with-app-sidebar': currentApp, 'icon-loading': loadingList }">
<div id="app-content" class="app-settings-content" :class="{ 'icon-loading': loadingList }">
<app-list :category="category" :app="currentApp" :search="searchQuery"></app-list>
<div id="app-sidebar" v-if="id && currentApp">
<app-details :category="category" :app="currentApp"></app-details>
</div>
</div>
<div id="app-sidebar" v-if="id && currentApp">
<app-details :category="category" :app="currentApp"></app-details>
</div>
</div>
</template>

View File

@ -21,7 +21,7 @@
-->
<template>
<div id="app">
<div id="content" class="app-settings">
<app-navigation :menu="menu">
<template slot="settings-content">
<div>

View File

@ -6,7 +6,7 @@ Feature: app-files
And I see that "welcome.txt" is marked as favorite
And I open the "Favorites" section
And I open the details view for "welcome.txt"
And I see that the details view for "Favorites" section is open
And I see that the details view is open
When I view "welcome.txt" in folder
Then I see that the current section is "All files"
And I see that the details view is closed
@ -17,11 +17,11 @@ Feature: app-files
And I see that "welcome.txt" is marked as favorite
And I open the "Favorites" section
And I open the details view for "welcome.txt"
And I see that the details view for "Favorites" section is open
And I see that the details view is open
And I view "welcome.txt" in folder
And I see that the current section is "All files"
When I open the details view for "welcome.txt"
Then I see that the details view for "All files" section is open
Then I see that the details view is open
Scenario: rename a file with the details view open
Given I am logged in
@ -151,14 +151,14 @@ Feature: app-files
Scenario: show the input field for tags in the details view
Given I am logged in
And I open the details view for "welcome.txt"
And I see that the details view for "All files" section is open
And I see that the details view is open
When I open the input field for tags in the details view
Then I see that the input field for tags in the details view is shown
Scenario: show the input field for tags in the details view after the sharing tab has loaded
Given I am logged in
And I open the details view for "welcome.txt"
And I see that the details view for "All files" section is open
And I see that the details view is open
And I open the "Sharing" tab in the details view
And I see that the "Sharing" tab in the details view is eventually loaded
When I open the input field for tags in the details view

View File

@ -31,8 +31,8 @@ class CommentsAppContext implements Context, ActorAwareInterface {
*/
public static function newCommentField() {
return Locator::forThe()->css("div.newCommentRow .message")->
descendantOf(FilesAppContext::currentSectionDetailsView())->
describedAs("New comment field in current section details view in Files app");
descendantOf(FilesAppContext::detailsView())->
describedAs("New comment field in details view in Files app");
}
/**
@ -40,8 +40,8 @@ class CommentsAppContext implements Context, ActorAwareInterface {
*/
public static function submitNewCommentButton() {
return Locator::forThe()->css("div.newCommentRow .submit")->
descendantOf(FilesAppContext::currentSectionDetailsView())->
describedAs("Submit new comment button in current section details view in Files app");
descendantOf(FilesAppContext::detailsView())->
describedAs("Submit new comment button in details view in Files app");
}
/**
@ -49,8 +49,8 @@ class CommentsAppContext implements Context, ActorAwareInterface {
*/
public static function commentList() {
return Locator::forThe()->css("ul.comments")->
descendantOf(FilesAppContext::currentSectionDetailsView())->
describedAs("Comment list in current section details view in Files app");
descendantOf(FilesAppContext::detailsView())->
describedAs("Comment list in details view in Files app");
}
/**
@ -59,7 +59,7 @@ class CommentsAppContext implements Context, ActorAwareInterface {
public static function commentWithText($text) {
return Locator::forThe()->xpath("//div[normalize-space() = '$text']/ancestor::li")->
descendantOf(self::commentList())->
describedAs("Comment with text \"$text\" in current section details view in Files app");
describedAs("Comment with text \"$text\" in details view in Files app");
}
/**

View File

@ -63,19 +63,9 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
public static function detailsViewForSection($section) {
return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
descendantOf(self::mainViewForSection($section))->
describedAs("Details view for section $section in Files app");
}
/**
* @return Locator
*/
public static function currentSectionDetailsView() {
return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
descendantOf(self::currentSectionMainView())->
describedAs("Current section details view in Files app");
public static function detailsView() {
return Locator::forThe()->id("app-sidebar")->
describedAs("Details view in Files app");
}
/**
@ -83,53 +73,53 @@ class FilesAppContext implements Context, ActorAwareInterface {
*/
public static function closeDetailsViewButton() {
return Locator::forThe()->css(".icon-close")->
descendantOf(self::currentSectionDetailsView())->
describedAs("Close current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("Close details view in Files app");
}
/**
* @return Locator
*/
public static function fileNameInCurrentSectionDetailsView() {
public static function fileNameInDetailsView() {
return Locator::forThe()->css(".fileName")->
descendantOf(self::currentSectionDetailsView())->
describedAs("File name in current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("File name in details view in Files app");
}
/**
* @return Locator
*/
public static function fileDetailsInCurrentSectionDetailsViewWithText($fileDetailsText) {
public static function fileDetailsInDetailsViewWithText($fileDetailsText) {
return Locator::forThe()->xpath("//span[normalize-space() = '$fileDetailsText']")->
descendantOf(self::fileDetailsInCurrentSectionDetailsView())->
describedAs("File details with text \"$fileDetailsText\" in current section details view in Files app");
descendantOf(self::fileDetailsInDetailsView())->
describedAs("File details with text \"$fileDetailsText\" in details view in Files app");
}
/**
* @return Locator
*/
private static function fileDetailsInCurrentSectionDetailsView() {
private static function fileDetailsInDetailsView() {
return Locator::forThe()->css(".file-details")->
descendantOf(self::currentSectionDetailsView())->
describedAs("File details in current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("File details in details view in Files app");
}
/**
* @return Locator
*/
public static function inputFieldForTagsInCurrentSectionDetailsView() {
public static function inputFieldForTagsInDetailsView() {
return Locator::forThe()->css(".systemTagsInfoView")->
descendantOf(self::currentSectionDetailsView())->
describedAs("Input field for tags in current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("Input field for tags in details view in Files app");
}
/**
* @return Locator
*/
public static function itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag) {
public static function itemInInputFieldForTagsInDetailsViewForTag($tag) {
return Locator::forThe()->xpath("//span[normalize-space() = '$tag']")->
descendantOf(self::inputFieldForTagsInCurrentSectionDetailsView())->
describedAs("Item in input field for tags in current section details view for tag $tag in Files app");
descendantOf(self::inputFieldForTagsInDetailsView())->
describedAs("Item in input field for tags in details view for tag $tag in Files app");
}
/**
@ -161,37 +151,37 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
public static function tabHeaderInCurrentSectionDetailsViewNamed($tabHeaderName) {
public static function tabHeaderInDetailsViewNamed($tabHeaderName) {
return Locator::forThe()->xpath("//li[normalize-space() = '$tabHeaderName']")->
descendantOf(self::tabHeadersInCurrentSectionDetailsView())->
describedAs("Tab header named $tabHeaderName in current section details view in Files app");
descendantOf(self::tabHeadersInDetailsView())->
describedAs("Tab header named $tabHeaderName in details view in Files app");
}
/**
* @return Locator
*/
private static function tabHeadersInCurrentSectionDetailsView() {
private static function tabHeadersInDetailsView() {
return Locator::forThe()->css(".tabHeaders")->
descendantOf(self::currentSectionDetailsView())->
describedAs("Tab headers in current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("Tab headers in details view in Files app");
}
/**
* @return Locator
*/
public static function tabInCurrentSectionDetailsViewNamed($tabName) {
public static function tabInDetailsViewNamed($tabName) {
return Locator::forThe()->xpath("//div[@id=//*[contains(concat(' ', normalize-space(@class), ' '), ' tabHeader ') and normalize-space() = '$tabName']/@data-tabid]")->
descendantOf(self::currentSectionDetailsView())->
describedAs("Tab named $tabName in current section details view in Files app");
descendantOf(self::detailsView())->
describedAs("Tab named $tabName in details view in Files app");
}
/**
* @return Locator
*/
public static function loadingIconForTabInCurrentSectionDetailsViewNamed($tabName) {
public static function loadingIconForTabInDetailsViewNamed($tabName) {
return Locator::forThe()->css(".loading")->
descendantOf(self::tabInCurrentSectionDetailsViewNamed($tabName))->
describedAs("Loading icon for tab named $tabName in current section details view in Files app");
descendantOf(self::tabInDetailsViewNamed($tabName))->
describedAs("Loading icon for tab named $tabName in details view in Files app");
}
/**
@ -202,7 +192,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
// return the checkbox itself, but the element that the user interacts
// with is the label.
return Locator::forThe()->xpath("//label[normalize-space() = 'Share link']")->
descendantOf(self::currentSectionDetailsView())->
descendantOf(self::detailsView())->
describedAs("Share link checkbox in the details view in Files app");
}
@ -210,7 +200,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function shareLinkField() {
return Locator::forThe()->css(".linkText")->descendantOf(self::currentSectionDetailsView())->
return Locator::forThe()->css(".linkText")->descendantOf(self::detailsView())->
describedAs("Share link field in the details view in Files app");
}
@ -222,7 +212,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
// that would return the radio button itself, but the element that the
// user interacts with is the label.
return Locator::forThe()->xpath("//label[normalize-space() = 'Allow upload and editing']")->
descendantOf(self::currentSectionDetailsView())->
descendantOf(self::detailsView())->
describedAs("Allow upload and editing radio button in the details view in Files app");
}
@ -234,7 +224,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
// would return the checkbox itself, but the element that the user
// interacts with is the label.
return Locator::forThe()->xpath("//label[normalize-space() = 'Password protect']")->
descendantOf(self::currentSectionDetailsView())->
descendantOf(self::detailsView())->
describedAs("Password protect checkbox in the details view in Files app");
}
@ -242,7 +232,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function passwordProtectField() {
return Locator::forThe()->css(".linkPassText")->descendantOf(self::currentSectionDetailsView())->
return Locator::forThe()->css(".linkPassText")->descendantOf(self::detailsView())->
describedAs("Password protect field in the details view in Files app");
}
@ -250,7 +240,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function passwordProtectWorkingIcon() {
return Locator::forThe()->css(".linkPass .icon-loading-small")->descendantOf(self::currentSectionDetailsView())->
return Locator::forThe()->css(".linkPass .icon-loading-small")->descendantOf(self::detailsView())->
describedAs("Password protect working icon in the details view in Files app");
}
@ -265,14 +255,14 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @Given I open the input field for tags in the details view
*/
public function iOpenTheInputFieldForTagsInTheDetailsView() {
$this->actor->find(self::fileDetailsInCurrentSectionDetailsViewWithText("Tags"), 10)->click();
$this->actor->find(self::fileDetailsInDetailsViewWithText("Tags"), 10)->click();
}
/**
* @Given I open the :tabName tab in the details view
*/
public function iOpenTheTabInTheDetailsView($tabName) {
$this->actor->find(self::tabHeaderInCurrentSectionDetailsViewNamed($tabName), 10)->click();
$this->actor->find(self::tabHeaderInDetailsViewNamed($tabName), 10)->click();
}
/**
@ -347,35 +337,29 @@ class FilesAppContext implements Context, ActorAwareInterface {
}
/**
* @Then I see that the details view for :section section is open
* @Then I see that the details view is open
*/
public function iSeeThatTheDetailsViewForSectionIsOpen($section) {
PHPUnit_Framework_Assert::assertTrue(
$this->actor->find(self::detailsViewForSection($section), 10)->isVisible());
$otherSections = self::sections();
unset($otherSections[$section]);
$this->assertDetailsViewForSectionsAreClosed($otherSections);
public function iSeeThatTheDetailsViewIsOpen() {
// The sidebar always exists in the DOM, so it has to be explicitly
// waited for it to be visible instead of relying on the implicit wait
// made to find the element.
if (!WaitFor::elementToBeEventuallyShown(
$this->actor,
self::detailsView(),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The details view is not open yet after $timeout seconds");
}
}
/**
* @Then I see that the details view is closed
*/
public function iSeeThatTheDetailsViewIsClosed() {
PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::currentSectionMainView(), 10));
$this->assertDetailsViewForSectionsAreClosed(self::sections());
}
private function assertDetailsViewForSectionsAreClosed($sections) {
foreach ($sections as $section => $id) {
try {
PHPUnit_Framework_Assert::assertFalse(
$this->actor->find(self::detailsViewForSection($section))->isVisible(),
"Details view for section $section is open but it should be closed");
} catch (NoSuchElementException $exception) {
}
if (!WaitFor::elementToBeEventuallyNotShown(
$this->actor,
self::detailsView(),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The details view is not closed yet after $timeout seconds");
}
}
@ -384,7 +368,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
*/
public function iSeeThatTheFileNameShownInTheDetailsViewIs($fileName) {
PHPUnit_Framework_Assert::assertEquals(
$this->actor->find(self::fileNameInCurrentSectionDetailsView(), 10)->getText(), $fileName);
$this->actor->find(self::fileNameInDetailsView(), 10)->getText(), $fileName);
}
/**
@ -392,7 +376,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
*/
public function iSeeThatTheInputFieldForTagsInTheDetailsViewIsShown() {
PHPUnit_Framework_Assert::assertTrue(
$this->actor->find(self::inputFieldForTagsInCurrentSectionDetailsView(), 10)->isVisible());
$this->actor->find(self::inputFieldForTagsInDetailsView(), 10)->isVisible());
}
/**
@ -400,7 +384,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
*/
public function iSeeThatTheInputFieldForTagsInTheDetailsViewContainsTheTag($tag) {
PHPUnit_Framework_Assert::assertTrue(
$this->actor->find(self::itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag), 10)->isVisible());
$this->actor->find(self::itemInInputFieldForTagsInDetailsViewForTag($tag), 10)->isVisible());
}
/**
@ -411,7 +395,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
try {
PHPUnit_Framework_Assert::assertFalse(
$this->actor->find(self::itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag))->isVisible());
$this->actor->find(self::itemInInputFieldForTagsInDetailsViewForTag($tag))->isVisible());
} catch (NoSuchElementException $exception) {
}
}
@ -441,7 +425,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) {
if (!WaitFor::elementToBeEventuallyNotShown(
$this->actor,
self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName),
self::loadingIconForTabInDetailsViewNamed($tabName),
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds");
}

View File

@ -1,23 +1,23 @@
/**
* ownCloud
*
* @author Vincent Petry
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
* ownCloud
*
* @author Vincent Petry
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This node module is run by the karma executable to specify its configuration.
@ -35,7 +35,6 @@
/* jshint node: true */
module.exports = function(config) {
function findApps() {
/*
var fs = require('fs');
@ -115,12 +114,7 @@ module.exports = function(config) {
name: 'settings',
srcFiles: [
'settings/js/apps.js',
'settings/js/users/deleteHandler.js',
'core/vendor/marked/marked.min.js'
],
testFiles: [
'settings/tests/js/appsSpec.js',
'settings/tests/js/users/deleteHandlerSpec.js'
]
}
];
@ -130,14 +124,16 @@ module.exports = function(config) {
// it is useful to disable coverage for debugging
// because the coverage preprocessor will wrap the JS files somehow
var enableCoverage = !parseInt(process.env.NOCOVERAGE, 10);
console.log('Coverage preprocessor: ', enableCoverage?'enabled':'disabled');
console.log(
'Coverage preprocessor: ',
enableCoverage ? 'enabled' : 'disabled'
);
// default apps to test when none is specified (TODO: read from filesystem ?)
var appsToTest = process.env.KARMA_TESTSUITE;
if (appsToTest) {
if (appsToTest) {
appsToTest = appsToTest.split(' ');
}
else {
} else {
appsToTest = ['core'].concat(findApps());
}
@ -167,19 +163,19 @@ module.exports = function(config) {
var srcFile, i;
// add vendor library files
for ( i = 0; i < coreModule.vendor.length; i++ ) {
for (i = 0; i < coreModule.vendor.length; i++) {
srcFile = vendorPath + coreModule.vendor[i];
files.push(srcFile);
}
// add core library files
for ( i = 0; i < coreModule.libraries.length; i++ ) {
for (i = 0; i < coreModule.libraries.length; i++) {
srcFile = corePath + coreModule.libraries[i];
files.push(srcFile);
}
// add core modules files
for ( i = 0; i < coreModule.modules.length; i++ ) {
for (i = 0; i < coreModule.modules.length; i++) {
srcFile = corePath + coreModule.modules[i];
files.push(srcFile);
if (enableCoverage) {
@ -197,7 +193,7 @@ module.exports = function(config) {
function addApp(app) {
// if only a string was specified, expand to structure
if (typeof(app) === 'string') {
if (typeof app === 'string') {
app = {
srcFiles: 'apps/' + app + '/js/**/*.js',
testFiles: 'apps/' + app + '/tests/js/**/*.js'
@ -217,23 +213,47 @@ module.exports = function(config) {
}
// add source files for apps to test
for ( i = 0; i < appsToTest.length; i++ ) {
for (i = 0; i < appsToTest.length; i++) {
addApp(appsToTest[i]);
}
// serve images to avoid warnings
files.push({pattern: 'core/img/**/*', watched: false, included: false, served: true});
files.push({pattern: 'core/css/images/*', watched: false, included: false, served: true});
files.push({
pattern: 'core/img/**/*',
watched: false,
included: false,
served: true
});
files.push({
pattern: 'core/css/images/*',
watched: false,
included: false,
served: true
});
// include core CSS
files.push({pattern: 'core/css/*.css', watched: true, included: true, served: true});
files.push({pattern: 'tests/css/*.css', watched: true, included: true, served: true});
files.push({
pattern: 'core/css/*.css',
watched: true,
included: true,
served: true
});
files.push({
pattern: 'tests/css/*.css',
watched: true,
included: true,
served: true
});
// Allow fonts
files.push({pattern: 'core/fonts/*', watched: false, included: false, served: true});
files.push({
pattern: 'core/fonts/*',
watched: false,
included: false,
served: true
});
config.set({
// base path, that will be used to resolve files and exclude
basePath: '..',
@ -244,9 +264,7 @@ module.exports = function(config) {
files: files,
// list of files to exclude
exclude: [
],
exclude: [],
proxies: {
// prevent warnings for images
@ -271,7 +289,7 @@ module.exports = function(config) {
preprocessors: preprocessors,
coverageReporter: {
dir:'tests/karma-coverage',
dir: 'tests/karma-coverage',
reporters: [
{ type: 'html' },
{ type: 'cobertura' },
@ -282,7 +300,6 @@ module.exports = function(config) {
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
@ -298,13 +315,30 @@ module.exports = function(config) {
// - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
// - PhantomJS
// - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
// use PhantomJS_debug for extra local debug
browsers: ['PhantomJS'],
plugins: [
'karma-phantomjs-launcher',
'karma-coverage',
'karma-jasmine',
'karma-jasmine-sinon',
'karma-viewport',
'karma-junit-reporter'
],
// you can define custom flags
customLaunchers: {
PhantomJS_debug: {
base: 'PhantomJS',
debug: true
}
},
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
});
});
};