Stream Fixes + Tests

This commit is contained in:
buddha87 2016-10-31 23:39:56 +01:00
parent 1fb8347a69
commit ac1fe194b9
37 changed files with 6023 additions and 164 deletions

View File

@ -9,159 +9,140 @@ humhub.initModule('client', function (module, require, $) {
/**
* Response Wrapper Object for easily accessing common data
*/
var Response = function (data, dataType) {
if(!dataType || dataType === 'json') {
$.extend(this, data);
} else if(dataType) {
this[dataType] = data;
} else {
this.data = data;
}
};
/**
* Checks if the response is a confirmation of the request
* @returns {Boolean}
*/
Response.prototype.isConfirmation = function () {
return this.getStatus() === 0;
};
//TODO: isValidationError status 2
/**
* Checks if the response is marke
* @returns {humhub.client_L5.Response.data.status|Boolean}
*/
Response.prototype.isError = function () {
return this.getStatus() > 0 || this.getErrors().length;
};
Response.prototype.getStatus = function () {
return (this.status) ? this.status : -1;
};
Response.prototype.getFirstError = function () {
var errors = this.getErrors();
if (errors.length) {
return errors[0];
}
};
Response.prototype.setAjaxError = function (xhr, errorThrown, textStatus, data, status) {
this.xhr = xhr;
var Response = function (xhr, url, textStatus, dataType) {
this.url = url;
this.status = xhr.status;
this.response = xhr.responseJSON || xhr.responseText;
//Textstatus = "timeout", "error", "abort", "parsererror", "application"
this.textStatus = textStatus;
this.status = status || xhr.status;
this.errors = [errorThrown];
this.dataType = dataType;
if (!dataType || dataType === 'json') {
$.extend(this, this.response);
} else if (dataType) {
this[dataType] = this.response;
}
};
/**
* Returns an array of errors or an empty array so getErrors().length is always
* safe.
* @returns {array} error array or empty array
*/
Response.prototype.getErrors = function () {
var errors = this.errors || [];
return (object.isString(errors)) ? [errors] : errors;
Response.prototype.setSuccess = function (data) {
this.data = data;
return this;
};
Response.prototype.toString = function () {
return "{ status: " + this.getStatus() + " error: " + this.getErrors() + " textStatus: " + this.textStatus + " }";
Response.prototype.setError = function (errorThrown) {
this.error = errorThrown;
this.validationError = (this.status === 400);
return this;
};
Response.prototype.isError = function () {
return this.status >= 400;
};
Response.prototype.getLog = function () {
var result = $.extend({}, this);
if(this.response && object.isString(this.response)) {
result.response = this.response.substr(0,200)
result.response += (this.response.length > 200) ? '...' : '';
};
if(this.html && object.isString(this.html)) {
result.html = this.html.substr(0,200)
result.html += (this.html.length > 200) ? '...' : '';
};
return result;
};
var submit = function ($form, cfg, originalEvent) {
if($form instanceof $.Event && $form.$form) {
if ($form instanceof $.Event && $form.$form) {
originalEvent = $form;
$form = $form.$form;
} else if(cfg instanceof $.Event) {
} else if (cfg instanceof $.Event) {
originalEvent = cfg;
cfg = {};
}
cfg = cfg || {};
$form = object.isString($form) ? $($form) : $form;
cfg.type = $form.attr('method') || 'post';
cfg.data = $form.serialize();
var url = cfg.url|| $form.attr('action');
var url = cfg.url || $form.attr('action');
return ajax(url, cfg, originalEvent);
};
var post = function (path, cfg, originalEvent) {
if(path instanceof $.Event) {
originalEvent = path;
path = originalEvent.url;
} else if(cfg instanceof $.Event) {
var post = function (url, cfg, originalEvent) {
if (url instanceof $.Event) {
originalEvent = url;
url = originalEvent.url;
} else if (cfg instanceof $.Event) {
originalEvent = cfg;
cfg = {};
}
cfg = cfg || {};
cfg.type = cfg.method = 'POST';
return ajax(path, cfg, originalEvent);
};
var get = function (path, cfg, originalEvent) {
if(path instanceof $.Event) {
originalEvent = path;
path = originalEvent.url;
} else if(cfg instanceof $.Event) {
originalEvent = cfg;
cfg = {};
}
cfg = cfg || {};
cfg.type = cfg.method = 'GET';
return ajax(path, cfg, originalEvent);
return ajax(url, cfg, originalEvent);
};
var ajax = function (path, cfg, originalEvent) {
// support for ajax(path, event) and ajax(path, successhandler);
if(cfg instanceof $.Event) {
var get = function (url, cfg, originalEvent) {
if (url instanceof $.Event) {
originalEvent = url;
url = originalEvent.url;
} else if (cfg instanceof $.Event) {
originalEvent = cfg;
cfg = {};
} else if(object.isFunction(cfg)) {
cfg = {'success' : cfg};
}
cfg = cfg || {};
cfg.type = cfg.method = 'GET';
return ajax(url, cfg, originalEvent);
};
var ajax = function (url, cfg, originalEvent) {
// support for ajax(url, event) and ajax(path, successhandler);
if (cfg instanceof $.Event) {
originalEvent = cfg;
cfg = {};
} else if (object.isFunction(cfg)) {
cfg = {'success': cfg};
}
var promise = new Promise(function (resolve, reject) {
cfg = cfg || {};
//Wrap the actual error handler with our own and call
var errorHandler = cfg.error;
var error = function (xhr, textStatus, errorThrown, data) {
//Textstatus = "timeout", "error", "abort", "parsererror", "application"
var error = function (xhr, textStatus, errorThrown) {
var response = new Response(xhr, url, textStatus, cfg.dataType).setError(errorThrown);
if (errorHandler && object.isFunction(errorHandler)) {
var response = new Response();
response.setAjaxError(xhr, errorThrown, textStatus, data, xhr.status);
errorHandler(response);
}
finish(originalEvent);
reject({'url' : path, 'textStatus': textStatus, 'response': xhr.responseJSON, 'error': errorThrown, 'data': data, 'status': xhr.status});
reject(response);
};
var successHandler = cfg.success;
var success = function (data, textStatus, xhr) {
var response = new Response(data, cfg.dataType);
if (response.isError()) { //Application errors
return error(xhr, "application", response.getErrors(), data, response.getStatus());
} else if (successHandler) {
response.textStatus = textStatus;
response.xhr = xhr;
var response = new Response(xhr, url, textStatus, cfg.dataType).setSuccess(data);
if (successHandler) {
successHandler(response);
}
finish(originalEvent);
resolve(response);
// Other modules can register global handler by the response type given by the backend.
// For example {type:'modal', 'content': '...')
if(response.type) {
event.trigger('humhub:modules:client:response:'+response.type);
if (response.type) {
event.trigger('humhub:modules:client:response:' + response.type);
}
promise.done(function() {
promise.done(function () {
// If content with <link> tags are inserted in resolve, the ajaxComplete handler in yii.js
// makes sure redundant stylesheets are removed. Here we get sure it is called after inserting the response.
$(document).trigger('ajaxComplete');
@ -171,28 +152,55 @@ humhub.initModule('client', function (module, require, $) {
//Overwriting the handler with our wrapper handler
cfg.success = success;
cfg.error = error;
cfg.url = path;
cfg.url = url;
//Setting some default values
cfg.dataType = cfg.dataType || "json";
$.ajax(cfg);
});
promise.status = function (setting) {
return new Promise(function (resolve, reject) {
promise.then(function (response) {
try {
if (setting[response.status]) {
setting[response.status](response);
}
resolve(response);
} catch (e) {
reject(e);
}
}).catch(function (response) {
try {
if (setting[response.status]) {
setting[response.status](response);
resolve(response);
} else {
reject(response);
}
} catch (e) {
reject(e);
}
});
});
};
return promise;
};
var finish = function(originalEvent) {
if(originalEvent && object.isFunction(originalEvent.finish)) {
var finish = function (originalEvent) {
if (originalEvent && object.isFunction(originalEvent.finish)) {
originalEvent.finish();
}
};
module.export({
ajax: ajax,
post: post,
get: get,
submit: submit
submit: submit,
Response: Response
});
});

View File

@ -8,6 +8,7 @@ humhub.initModule('ui.status', function (module, require, $) {
var event = require('event');
var log = require('log');
var object = require('util').object;
var client = require('client');
var SELECTOR_ROOT = '#status-bar';
var SELECTOR_BODY = '.status-bar-body';
@ -113,9 +114,12 @@ humhub.initModule('ui.status', function (module, require, $) {
if (error.error instanceof Error) {
error.stack = (error.error.stack) ? error.error.stack : undefined;
error.error = error.error.message;
} else if(error instanceof client.Response) {
error = error.getLog();
}
try {
return JSON.stringify(error, null, 4);
// encode
return $('<div/>').text(JSON.stringify(error, null, 4)).html();
} catch(e) {
return error.toString();
}

View File

@ -44,6 +44,20 @@ humhub.initModule('util', function(module, require, $) {
inherits: function(Sub, Parent) {
Sub.prototype = Object.create(Parent.prototype);
Sub._super = Parent.prototype;
Sub.prototype.super = function(method) {
if(!Sub._super[arguments[0]]) {
throw new Error('Call of undefined method of super type');
}
var args;
if(arguments.length > 1){
args = [];
Array.prototype.push.apply( args, arguments );
args.shift();
}
return Sub._super[arguments[0]].apply(this, args);
};
}
};

View File

@ -47,13 +47,13 @@ class CoreApiAsset extends AssetBundle
'js/humhub/humhub.util.js',
'js/humhub/humhub.log.js',
//'js/humhub/humhub.scripts.js',
'js/humhub/humhub.ui.status.js',
'js/humhub/humhub.ui.additions.js',
'js/humhub/humhub.ui.loader.js',
'js/humhub/humhub.ui.modal.js',
'js/humhub/humhub.client.js',
'js/humhub/humhub.client.pjax.js',
'js/humhub/humhub.action.js'
'js/humhub/humhub.action.js',
'js/humhub/humhub.ui.status.js'
];
/**

View File

@ -85,7 +85,7 @@ humhub.initModule('activity', function (module, require, $) {
var stream = getStream();
if (!stream) {
console.log('No activity stream found!');
module.log.info('No activity stream found!');
return;
}
@ -96,16 +96,16 @@ humhub.initModule('activity', function (module, require, $) {
// listen for scrolling event yes or no
var scrolling = true;
stream.$content.scroll(function () {
stream.$content.scroll(function (evt) {
// save height of the overflow container
var _containerHeight = $("#activityContents").height();
var _containerHeight = stream.$content.height();
// save scroll height
var _scrollHeight = $("#activityContents").prop("scrollHeight");
var _scrollHeight = stream.$content.prop("scrollHeight");
// save current scrollbar position
var _currentScrollPosition = $('#activityContents').scrollTop();
var _currentScrollPosition = stream.$content.scrollTop();
// load more activites if current scroll position is near scroll height
if (_currentScrollPosition >= (_scrollHeight - _containerHeight - 30)) {

View File

@ -31,6 +31,7 @@ $this->registerJsVar('activityInfoUrl', $infoUrl);
cursorborder: "",
cursorcolor: "#555",
cursoropacitymax: "0.2",
nativeparentscrolling: false,
railpadding: {top: 0, right: 3, left: 0, bottom: 0}
});

View File

@ -0,0 +1,24 @@
actor: Tester
namespace: comment
settings:
bootstrap: _bootstrap.php
suite_class: \PHPUnit_Framework_TestSuite
colors: true
shuffle: false
memory_limit: 1024M
log: true
# This value controls whether PHPUnit attempts to backup global variables
# See https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.backupGlobals
backup_globals: true
paths:
tests: codeception
log: codeception/_output
data: codeception/_data
helpers: codeception/_support
envs: config/env
config:
# the entry script URL (with host info) for functional and acceptance tests
# PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
test_entry_url: http://localhost:8080/index-test.php

View File

@ -0,0 +1,57 @@
<?php
/**
* This is the initial test bootstrap, which will load the default test bootstrap from the humhub core
*/
// Parse the environment arguments (Note: only simple --env ENV is supported no comma sepration merge...)
$argv = $_SERVER['argv'];
$env = [];
for ($i = 0; $i < count($argv); $i++) {
if ($argv[$i] === '--env') {
$env[] = explode(',', $argv[++$i]);
}
}
// If environment was set try loading special environment config else load default
if (count($env) > 0) {
\Codeception\Configuration::append(['environment' => $env]);
echo 'Run execution environment: ' . $env[0][0] . PHP_EOL;
$envCfgFile = dirname(__DIR__) . '/config/env/test.' . $env[0][0] . '.php';
if (file_exists($envCfgFile)) {
$cfg = array_merge(require_once(__DIR__ . '/../config/test.php'), require_once($envCfgFile));
}
}
// If no environment is set we have to load the default config
if (!isset($cfg)) {
$cfg = require_once(__DIR__ . '/../config/test.php');
}
// If no humhub_root is given we assume our module is in the a root to be in /protected/humhub/modules/<module>/tests/codeception directory
$cfg['humhub_root'] = isset($cfg['humhub_root']) ? $cfg['humhub_root'] : dirname(__DIR__) . '/../../../../..';
echo 'Using HumHub Root: ' . $cfg['humhub_root'] . PHP_EOL;
// Load default test bootstrap
require_once($cfg['humhub_root'] . '/protected/humhub/tests/codeception/_bootstrap.php');
// Overwrite the default test alias
Yii::setAlias('@tests', dirname(__DIR__));
Yii::setAlias('@env', '@tests/config/env');
Yii::setAlias('@root', $cfg['humhub_root']);
Yii::setAlias('@humhubTests', $cfg['humhub_root'] . '/protected/humhub/tests');
// Load all supporting test classes needed for test execution
\Codeception\Util\Autoload::addNamespace('', Yii::getAlias('@humhubTests/codeception/_support'));
\Codeception\Util\Autoload::addNamespace('tests\codeception\fixtures', Yii::getAlias('@humhubTests/codeception/fixtures'));
\Codeception\Util\Autoload::addNamespace('', Yii::getAlias('@humhubTests/codeception/_pages'));
if(isset($cfg['modules'])) {
\Codeception\Configuration::append(['humhub_modules' => $cfg['modules']]);
}
if(isset($cfg['fixtures'])) {
\Codeception\Configuration::append(['fixtures' => $cfg['fixtures']]);
}
?>

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -0,0 +1,26 @@
<?php
namespace comment;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class AcceptanceTester extends \AcceptanceTester
{
use _generated\AcceptanceTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,26 @@
<?php
namespace comment;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class FunctionalTester extends \FunctionalTester
{
use _generated\FunctionalTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,26 @@
<?php
namespace comment;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class UnitTester extends \UnitTester
{
use _generated\UnitTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,38 @@
<?php //[STAMP] 0468b7e2480518455b63a22d3aa6f7c2
namespace stream\_generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
// @codingStandardsIgnoreFile
use tests\codeception\_support\CodeHelper;
trait UnitTesterActions
{
/**
* @return \Codeception\Scenario
*/
abstract protected function getScenario();
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \tests\codeception\_support\CodeHelper::assertContainsError()
*/
public function assertContainsError($model, $message) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContainsError', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \tests\codeception\_support\CodeHelper::assertNotContainsError()
*/
public function assertNotContainsError($model, $message) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContainsError', func_get_args()));
}
}

View File

@ -0,0 +1,22 @@
# Codeception Test Suite Configuration
# suite for acceptance tests.
# perform tests in browser using the Selenium-like tools.
# powered by Mink (http://mink.behat.org).
# (tip: that's what your customer will see).
# (tip: test your ajax and javascript by one of Mink drivers).
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: AcceptanceTester
modules:
enabled:
- WebDriver
- tests\codeception\_support\WebHelper
- tests\codeception\_support\DynamicFixtureHelper
config:
WebDriver:
url: http://localhost:8080/
browser: phantomjs
window_size: 1024x768
lang: en

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/acceptance/_bootstrap.php'));

View File

@ -0,0 +1,3 @@
<?php
return \tests\codeception\_support\HumHubTestConfiguration::getSuiteConfig('functional');

View File

@ -0,0 +1,3 @@
<?php
return \tests\codeception\_support\HumHubTestConfiguration::getSuiteConfig('unit');

View File

@ -0,0 +1,19 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\comment\tests\codeception\fixtures;
use yii\test\ActiveFixture;
class CommentFixture extends ActiveFixture
{
public $modelClass = 'humhub\modules\comment\models\Comment';
public $dataFile = '@modules/comment/tests/codeception/fixtures/data/comment.php';
}

View File

@ -0,0 +1,9 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
return [];

View File

@ -0,0 +1,18 @@
# Codeception Test Suite Configuration
# suite for functional (integration) tests.
# emulate web requests and make application process them.
# (tip: better to use with frameworks).
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: FunctionalTester
modules:
enabled:
- Filesystem
- Yii2
- tests\codeception\_support\TestHelper
- tests\codeception\_support\DynamicFixtureHelper
- tests\codeception\_support\HumHubHelper
config:
Yii2:
configFile: 'codeception/config/functional.php'

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/functional/_bootstrap.php'));

View File

@ -0,0 +1,9 @@
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: UnitTester
modules:
enabled:
- tests\codeception\_support\CodeHelper

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/unit/_bootstrap.php'));

View File

@ -0,0 +1,5 @@
<?php
/**
* This config is shared by all suites (unit/functional/acceptance) and can be overwritten by a suite config (e.g. functional.php)
*/
return [];

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -0,0 +1,5 @@
<?php
/**
* Here you can overwrite the default config for the functional suite. The default config resides in @humhubTests/codeception/config/config.php
*/
return [];

View File

@ -0,0 +1,11 @@
<?php
return [
'fixtures' => [
'default',
'content' => 'humhub\modules\content\tests\codeception\fixtures\ContentFixture',
]
];

View File

@ -0,0 +1,5 @@
<?php
/**
* Here you can overwrite your functional humhub config. The default config resiedes in @humhubTests/codeception/config/config.php
*/
return [];

View File

@ -18,7 +18,8 @@ class ContentFixture extends ActiveFixture
public $depends = [
'humhub\modules\content\tests\codeception\fixtures\ContentContainerFixture',
'humhub\modules\post\tests\codeception\fixtures\PostFixture'
'humhub\modules\post\tests\codeception\fixtures\PostFixture',
'humhub\modules\comment\tests\codeception\fixtures\CommentFixture'
];
}

View File

@ -4,7 +4,7 @@ use humhub\compat\CActiveForm;
?>
<div class="content_edit" id="post_edit_<?php echo $post->id; ?>">
<div class="content content_edit" id="post_edit_<?php echo $post->id; ?>">
<?php $form = CActiveForm::begin(['id' => 'post-edit-form_' . $post->id]); ?>
<!-- create contenteditable div for HEditorWidget to place the data -->

View File

@ -75,6 +75,8 @@ humhub.initModule('stream', function (module, require, $) {
*/
var StreamEntry = function (id) {
Content.call(this, id);
// Set the stream so we have it even if the entry is detached.
this.stream();
};
object.inherits(StreamEntry, Content);
@ -84,21 +86,23 @@ humhub.initModule('stream', function (module, require, $) {
};
StreamEntry.prototype.delete = function () {
// Search for a nestet content component or call default content delete
var content = this.getContentComponent();
var promise = (content && content.delete) ? content.delete()
: StreamEntry._super.delete.call(this);
// Either call delete of a nestet content component or call default content delete
var content = this.contentComponent();
var promise = (content && content.delete) ? content.delete() : this.super('delete');
var stream = this.stream();
promise.then(function ($confirm) {
if ($confirm) {
module.log.success('success.delete');
}
}).catch(function (err) {
module.log.error(err, true);
}).finally(function () {
stream.onChange();
});
};
StreamEntry.prototype.getContentComponent = function () {
StreamEntry.prototype.contentComponent = function () {
var children = this.children();
return children.length ? children[0] : undefined;
};
@ -121,6 +125,12 @@ humhub.initModule('stream', function (module, require, $) {
StreamEntry.prototype.edit = function (evt) {
var that = this;
if (this.$.data('lastEdit')) {
that.replaceContent(this.$.data('lastEdit'));
that.$.find('input[type="text"], textarea, [contenteditable="true"]').first().focus();
return;
}
client.get(evt, {
dataType: 'html',
beforeSend: function () {
@ -136,14 +146,15 @@ humhub.initModule('stream', function (module, require, $) {
});
// Listen to click events outside of the stream entry and cancel edit.
$('body').off('click.humhub:modules:stream:edit').on('click.humhub:modules:stream:edit', function (e) {
$('body').off('mousedown.humhub:modules:stream:edit').one('mousedown.humhub:modules:stream:edit', function (e) {
if (!$(e.target).closest('[data-content-key="' + that.getKey() + '"]').length) {
var $editContent = that.$.find('.content_edit:first');
that.$.data('lastEdit', $editContent.clone(true));
if ($editContent && that.$.data('oldContent')) {
$editContent.replaceWith(that.$.data('oldContent'));
that.$.data('oldContent', undefined);
}
$('body').off('click.humhub:modules:stream:edit');
$('body').off('mousedown.humhub:modules:stream:edit');
}
});
};
@ -162,12 +173,20 @@ humhub.initModule('stream', function (module, require, $) {
beforeSend: function () {
that.loader();
}
}).then(function (response) {
that.$.html(response.html);
module.log.success('success.edit');
that.highlight();
}).status({
200: function (response) {
that.$.html(response.html);
module.log.success('success.edit');
that.highlight();
},
400: function (response) {
that.replaceContent(response.html);
}
}).catch(function (e) {
module.log.error(e, true);
}).finally(function () {
$('body').off('mousedown.humhub:modules:stream:edit');
that.$.data('lastEdit', undefined);
that.loader(false);
});
};
@ -188,7 +207,7 @@ humhub.initModule('stream', function (module, require, $) {
};
StreamEntry.prototype.getContent = function () {
return this.$.find('.content:first');
return this.$.find('.content, .content_edit').first();
};
StreamEntry.prototype.highlight = function () {
@ -263,7 +282,7 @@ humhub.initModule('stream', function (module, require, $) {
} else {
that.remove().then(function () {
module.log.success('success.archive', true);
})
});
}
} else {
module.log.error(response, true);
@ -274,6 +293,12 @@ humhub.initModule('stream', function (module, require, $) {
});
};
StreamEntry.prototype.remove = function () {
var stream = this.stream();
return this.super('remove')
.then($.proxy(stream.onChange, stream));
};
StreamEntry.prototype.unarchive = function (evt) {
var that = this;
this.loader();
@ -291,7 +316,11 @@ humhub.initModule('stream', function (module, require, $) {
StreamEntry.prototype.stream = function () {
// Just return the parent stream component.
return this.parent();
if(!this.$.data('stream')) {
return this.$.data('stream', this.parent());
}
return this.$.data('stream');
};
/**
@ -553,12 +582,14 @@ humhub.initModule('stream', function (module, require, $) {
var $html = $(html).hide();
this.$content.prepend($html);
$html.fadeIn();
this.onChange();
};
Stream.prototype.appendEntry = function (html) {
var $html = $(html).hide();
this.$content.append($html);
$html.fadeIn();
this.onChange();
};

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -42,9 +42,7 @@ class StreamCest
$I->wantToTest('the archivation of a stream entry');
$I->amGoingTo('create a new post and archive it afterwards');
$I->click('#contentForm_message_contenteditable');
$I->fillField('#contentForm_message_contenteditable', 'This is my stream test post!');
$I->click('#post_submit_button');
$I->createPost('This is my stream test post!');
$newEntrySelector = '[data-content-key="12"]';
@ -62,7 +60,7 @@ class StreamCest
$I->dontSeeElement($newEntrySelector);
$I->amGoingTo('check if my post is visible with filter include archived');
$I->click('#filter .dropdown-toggle');
$I->click('Filter', '#filter');
$I->waitForElementVisible('#filter_entry_archived');
$I->click('#filter_entry_archived');
@ -95,12 +93,10 @@ class StreamCest
{
$I->amUser();
$I->amOnSpace2();
$I->wantToTest('the deletion of a stream entry');
$I->wantToTest('the stick of posts');
$I->amGoingTo('create a new post and delete it afterwards');
$I->click('#contentForm_message_contenteditable');
$I->fillField('#contentForm_message_contenteditable', 'This is my first stream test post!');
$I->click('#post_submit_button');
$I->createPost('This is my first stream test post!');
$newEntrySelector = '[data-content-key="12"]';
@ -108,9 +104,8 @@ class StreamCest
$I->see('This is my first stream test post', '.wall-entry');
$I->amGoingTo('create another post');
$I->click('#contentForm_message_contenteditable');
$I->fillField('#contentForm_message_contenteditable', 'This is my second stream test post!');
$I->click('#post_submit_button');
$I->createPost('This is my second stream test post!');
$newEntrySelector2 = '[data-content-key="14"]';
$I->waitForElementVisible($newEntrySelector2);
@ -139,33 +134,104 @@ class StreamCest
{
$I->amUser();
$I->amOnSpace2();
$I->wantToTest('the deletion of a stream entry');
$I->wantToTest('the edit post mechanism');
$I->amGoingTo('create a new post and delete it afterwards');
$I->click('#contentForm_message_contenteditable');
$I->fillField('#contentForm_message_contenteditable', 'This is my first stream test post!');
$I->click('#post_submit_button');
$I->createPost('This is my first stream test post!');
$newEntrySelector = '[data-content-key="12"]';
$I->waitForElementVisible($newEntrySelector);
$I->see('This is my first stream test post', '.wall-entry');
$I->amGoingTo('edit my new post');
$I->click('.preferences', $newEntrySelector);
$I->waitForText('Edit', 10);
$I->click('Edit', $newEntrySelector);
$I->waitForElementVisible($newEntrySelector.' .content_edit', 20);
$I->fillField($newEntrySelector.' [contenteditable]', 'This is my edited post!');
$I->click('Save', $newEntrySelector);;
$I->waitForElementVisible($newEntrySelector . ' .content_edit', 20);
$I->fillField($newEntrySelector . ' [contenteditable]', 'This is my edited post!');
$I->click('Save', $newEntrySelector);
;
$I->seeSuccess('Saved');
$I->seeElement($newEntrySelector);
$I->see('This is my edited post!', $newEntrySelector);
}
public function testEmptyStream(AcceptanceTester $I)
{
$I->amUser();
$I->amOnSpace3();
$I->wantToTest('the empty stream message and filter');
$I->waitForText('This space is still empty!');
$I->dontSeeElement('#filter');
$I->amGoingTo('create a new post and delete it afterwards');
$I->createPost('This is my first stream test post!');
$I->wait(1);
$I->amGoingTo('Delete my new post again.');
$I->dontSee('This space is still empty!');
$I->seeElement('#filter');
$I->click('.preferences', '[data-stream-entry]:nth-of-type(1)');
$I->wait(1);
$I->click('Delete');
$I->waitForElementVisible('#globalModalConfirm', 5);
$I->see('Do you really want to perform this action?');
$I->click('Confirm');
$I->seeSuccess('The content has been deleted');
$I->see('This space is still empty!');
$I->dontSeeElement('#filter');
}
public function testSortStream(AcceptanceTester $I)
{
$I->amUser();
$I->amOnSpace3();
$I->wantToTest('the stream entry ordering');
$I->amGoingTo('create a new post and delete it afterwards');
$I->createPost('POST1');
$I->createPost('POST2');
$I->createPost('POST3');
$I->createPost('POST4');
$I->createPost('POST5');
$I->see('POST5', '.s2_streamContent > [data-stream-entry]:nth-of-type(1)');
$I->see('POST4', '.s2_streamContent > [data-stream-entry]:nth-of-type(2)');
$I->see('POST3', '.s2_streamContent > [data-stream-entry]:nth-of-type(3)');
$I->see('POST2', '.s2_streamContent > [data-stream-entry]:nth-of-type(4)');
$I->see('POST1', '.s2_streamContent > [data-stream-entry]:nth-of-type(5)');
$post4Selector = '[data-stream-entry][data-content-key="18"]';
$I->click('Comment', $post4Selector);
$I->fillField($post4Selector . ' [contenteditable]', 'My Comment!');
$I->click('Send', $post4Selector . ' .comment-buttons');
$I->scrollTop();
$I->click('.stream-sorting', '#filter');
$I->waitForElementVisible('#sorting_u');
$I->click('#sorting_u');
$I->wait(2);
$I->waitForElementVisible($post4Selector);
$I->wait(20);
$I->see('POST4', '.s2_streamContent > [data-stream-entry]:nth-of-type(1)');
$I->see('POST5', '.s2_streamContent > [data-stream-entry]:nth-of-type(2)');
$I->see('POST3', '.s2_streamContent > [data-stream-entry]:nth-of-type(3)');
$I->see('POST2', '.s2_streamContent > [data-stream-entry]:nth-of-type(4)');
$I->see('POST1', '.s2_streamContent > [data-stream-entry]:nth-of-type(5)');
}
// Sorting
// Filtering
// multi click logic
// empty form

View File

@ -34,7 +34,7 @@ $this->registerJsVar('defaultStreamSort', ($defaultStreamSort != '') ? $defaultS
<?php if ($this->context->showFilters) { ?>
<ul class="nav nav-tabs wallFilterPanel" id="filter" style="display: none;">
<li class=" dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Filter'); ?> <b
<a class="stream-filter dropdown-toggle" data-toggle="dropdown" href="#"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Filter'); ?> <b
class="caret"></b></a>
<ul class="dropdown-menu">
<?php foreach ($filters as $filterId => $filterTitle): ?>
@ -47,7 +47,7 @@ $this->registerJsVar('defaultStreamSort', ($defaultStreamSort != '') ? $defaultS
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Sorting'); ?>
<a class="stream-sorting dropdown-toggle" data-toggle="dropdown" href="#"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Sorting'); ?>
<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#" class="wallSorting" id="sorting_c"><i

View File

@ -62,13 +62,21 @@ class AcceptanceTester extends \Codeception\Actor
$this->amOnPage('index-test.php?r=space/space&sguid=5396d499-20d6-4233-800b-c6c86e5fa34d');
}
public function createPost($text)
{
$this->click('#contentForm_message_contenteditable');
$this->fillField('#contentForm_message_contenteditable', $text);
$this->click('#post_submit_button');
$this->waitForText($text, 30, '.wall-entry');
}
public function seeSuccess($text = null)
{
$this->waitForElementVisible('#status-bar .success', 20);
$this->waitForElementVisible('#status-bar .success', 30);
if ($text) {
$this->see($text, '#status-bar');
}
$this->waitForElementVisible('#status-bar .status-bar-close');
$this->click('#status-bar .status-bar-close');
$this->waitForElementNotVisible('#status-bar');
@ -80,7 +88,7 @@ class AcceptanceTester extends \Codeception\Actor
if ($text) {
$this->see($text, '#status-bar');
}
$this->waitForElementVisible('#status-bar .status-bar-close');
$this->click('#status-bar .status-bar-close');
$this->waitForElementNotVisible('#status-bar');
@ -103,7 +111,7 @@ class AcceptanceTester extends \Codeception\Actor
if ($text) {
$this->see($text, '#status-bar');
}
$this->waitForElementVisible('#status-bar .status-bar-close');
$this->click('#status-bar .status-bar-close');
$this->waitForElementNotVisible('#status-bar');
@ -174,6 +182,12 @@ class AcceptanceTester extends \Codeception\Actor
$this->dontSee($text);
$this->click('.notifications');
}
public function scrollTop()
{
$this->executeJS('window.scrollTo(0,0);');
$this->wait(1);
}
public function jsClick($selector)
{