commentstabviewSpec.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. /**
  2. * ownCloud
  3. *
  4. * @author Vincent Petry
  5. * @copyright 2016 Vincent Petry <pvince81@owncloud.com>
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  9. * License as published by the Free Software Foundation; either
  10. * comment 3 of the License, or any later comment.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public
  18. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. describe('OCA.Comments.CommentsTabView tests', function() {
  22. var view, fileInfoModel;
  23. var fetchStub;
  24. var avatarStub;
  25. var testComments;
  26. var clock;
  27. /**
  28. * Creates a dummy message with the given length
  29. *
  30. * @param {int} len length
  31. * @return {string} message
  32. */
  33. function createMessageWithLength(len) {
  34. var bigMessage = '';
  35. for (var i = 0; i < len; i++) {
  36. bigMessage += 'a';
  37. }
  38. return bigMessage;
  39. }
  40. beforeEach(function() {
  41. clock = sinon.useFakeTimers(Date.UTC(2016, 1, 3, 10, 5, 9));
  42. fetchStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'fetchNext');
  43. avatarStub = sinon.stub($.fn, 'avatar');
  44. view = new OCA.Comments.CommentsTabView();
  45. fileInfoModel = new OCA.Files.FileInfoModel({
  46. id: 5,
  47. name: 'One.txt',
  48. mimetype: 'text/plain',
  49. permissions: 31,
  50. path: '/subdir',
  51. size: 123456789,
  52. etag: 'abcdefg',
  53. mtime: Date.UTC(2016, 1, 0, 0, 0, 0)
  54. });
  55. view.render();
  56. var comment1 = new OCA.Comments.CommentModel({
  57. id: 1,
  58. actorType: 'users',
  59. actorId: 'user1',
  60. actorDisplayName: 'User One',
  61. objectType: 'files',
  62. objectId: 5,
  63. message: 'First',
  64. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 0)).toUTCString()
  65. });
  66. var comment2 = new OCA.Comments.CommentModel({
  67. id: 2,
  68. actorType: 'users',
  69. actorId: 'user2',
  70. actorDisplayName: 'User Two',
  71. objectType: 'files',
  72. objectId: 5,
  73. message: 'Second\nNewline',
  74. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 0, 0)).toUTCString()
  75. });
  76. var comment3 = new OCA.Comments.CommentModel({
  77. id: 3,
  78. actorId: 'anotheruser',
  79. actorDisplayName: 'Another User',
  80. actorType: 'users',
  81. verb: 'comment',
  82. message: 'Hail to thee, @macbeth. Yours faithfully, @banquo',
  83. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
  84. mentions: {
  85. 0: {
  86. mentionDisplayName: "Thane of Cawdor",
  87. mentionId: "macbeth",
  88. mentionTye: "user"
  89. },
  90. 1: {
  91. mentionDisplayName: "Lord Banquo",
  92. mentionId: "banquo",
  93. mentionTye: "user"
  94. }
  95. }
  96. });
  97. testComments = [comment1, comment2, comment3];
  98. });
  99. afterEach(function() {
  100. view.remove();
  101. view = undefined;
  102. fetchStub.restore();
  103. avatarStub.restore();
  104. clock.restore();
  105. });
  106. describe('rendering', function() {
  107. it('reloads matching comments when setting file info model', function() {
  108. view.setFileInfo(fileInfoModel);
  109. expect(fetchStub.calledOnce).toEqual(true);
  110. });
  111. it('renders loading icon while fetching comments', function() {
  112. view.setFileInfo(fileInfoModel);
  113. view.collection.trigger('request');
  114. expect(view.$el.find('.loading').length).toEqual(1);
  115. expect(view.$el.find('.comments li').length).toEqual(0);
  116. });
  117. it('renders comments', function() {
  118. view.setFileInfo(fileInfoModel);
  119. view.collection.set(testComments);
  120. var $comments = view.$el.find('.comments>li');
  121. expect($comments.length).toEqual(3);
  122. var $item = $comments.eq(0);
  123. expect($item.find('.author').text()).toEqual('User One');
  124. expect($item.find('.date').text()).toEqual('seconds ago');
  125. expect($item.find('.message').text()).toEqual('First');
  126. $item = $comments.eq(1);
  127. expect($item.find('.author').text()).toEqual('User Two');
  128. expect($item.find('.date').text()).toEqual('5 minutes ago');
  129. expect($item.find('.message').html()).toEqual('Second<br>Newline');
  130. });
  131. it('renders comments from deleted user differently', function() {
  132. testComments[0].set('actorType', 'deleted_users', {silent: true});
  133. view.collection.set(testComments);
  134. var $item = view.$el.find('.comment[data-id=1]');
  135. expect($item.find('.author').text()).toEqual('[Deleted user]');
  136. expect($item.find('.avatar').attr('data-username')).not.toBeDefined();
  137. });
  138. it('renders mentioned user id to avatar and displayname', function() {
  139. view.collection.set(testComments);
  140. var $comment = view.$el.find('.comment[data-id=3] .message');
  141. expect($comment.length).toEqual(1);
  142. expect($comment.find('.avatar[data-user=macbeth]').length).toEqual(1);
  143. expect($comment.find('strong:first').text()).toEqual('Thane of Cawdor');
  144. expect($comment.find('.avatar[data-user=macbeth] ~ .contactsmenu-popover').length).toEqual(1);
  145. expect($comment.find('.avatar[data-user=banquo]').length).toEqual(1);
  146. expect($comment.find('.avatar[data-user=banquo] ~ strong').text()).toEqual('Lord Banquo');
  147. expect($comment.find('.avatar[data-user=banquo] ~ .contactsmenu-popover').length).toEqual(1);
  148. });
  149. });
  150. describe('more comments', function() {
  151. var hasMoreResultsStub;
  152. beforeEach(function() {
  153. view.collection.set(testComments);
  154. hasMoreResultsStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'hasMoreResults');
  155. });
  156. afterEach(function() {
  157. hasMoreResultsStub.restore();
  158. });
  159. it('shows "More comments" button when more comments are available', function() {
  160. hasMoreResultsStub.returns(true);
  161. view.collection.trigger('sync');
  162. expect(view.$el.find('.showMore').hasClass('hidden')).toEqual(false);
  163. });
  164. it('does not show "More comments" button when more comments are available', function() {
  165. hasMoreResultsStub.returns(false);
  166. view.collection.trigger('sync');
  167. expect(view.$el.find('.showMore').hasClass('hidden')).toEqual(true);
  168. });
  169. it('fetches and appends the next page when clicking the "More" button', function() {
  170. hasMoreResultsStub.returns(true);
  171. expect(fetchStub.notCalled).toEqual(true);
  172. view.$el.find('.showMore').trigger('click');
  173. expect(fetchStub.calledOnce).toEqual(true);
  174. });
  175. it('appends comment to the list when added to collection', function() {
  176. var comment4 = new OCA.Comments.CommentModel({
  177. id: 4,
  178. actorType: 'users',
  179. actorId: 'user3',
  180. actorDisplayName: 'User Three',
  181. objectType: 'files',
  182. objectId: 5,
  183. message: 'Third',
  184. creationDateTime: new Date(Date.UTC(2016, 1, 3, 5, 0, 0)).toUTCString()
  185. });
  186. view.collection.add(comment4);
  187. expect(view.$el.find('.comments>li').length).toEqual(4);
  188. var $item = view.$el.find('.comments>li').eq(3);
  189. expect($item.find('.author').text()).toEqual('User Three');
  190. expect($item.find('.date').text()).toEqual('5 hours ago');
  191. expect($item.find('.message').html()).toEqual('Third');
  192. });
  193. });
  194. describe('posting comments', function() {
  195. var createStub;
  196. var currentUserStub;
  197. var $newCommentForm;
  198. beforeEach(function() {
  199. view.collection.set(testComments);
  200. createStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'create');
  201. currentUserStub = sinon.stub(OC, 'getCurrentUser');
  202. currentUserStub.returns({
  203. uid: 'testuser',
  204. displayName: 'Test User'
  205. });
  206. $newCommentForm = view.$el.find('.newCommentForm');
  207. // Required for the absolute selector used to find the new comment
  208. // after a successful creation in _onSubmitSuccess.
  209. $('#testArea').append(view.$el);
  210. });
  211. afterEach(function() {
  212. createStub.restore();
  213. currentUserStub.restore();
  214. });
  215. it('creates a new comment when clicking post button', function() {
  216. $newCommentForm.find('.message').text('New message');
  217. $newCommentForm.submit();
  218. expect(createStub.calledOnce).toEqual(true);
  219. expect(createStub.lastCall.args[0]).toEqual({
  220. actorId: 'testuser',
  221. actorDisplayName: 'Test User',
  222. actorType: 'users',
  223. verb: 'comment',
  224. message: 'New message',
  225. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
  226. });
  227. });
  228. it('creates a new comment when typing enter', function() {
  229. $newCommentForm.find('.message').text('New message');
  230. var keydownEvent = new $.Event('keydown', {keyCode: 13});
  231. $newCommentForm.find('.message').trigger(keydownEvent);
  232. expect(createStub.calledOnce).toEqual(true);
  233. expect(createStub.lastCall.args[0]).toEqual({
  234. actorId: 'testuser',
  235. actorDisplayName: 'Test User',
  236. actorType: 'users',
  237. verb: 'comment',
  238. message: 'New message',
  239. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
  240. });
  241. expect(keydownEvent.isDefaultPrevented()).toEqual(true);
  242. });
  243. it('creates a new mention when typing enter in the autocomplete popover', function() {
  244. var autoCompleteStub = sinon.stub(view, '_onAutoComplete');
  245. autoCompleteStub.callsArgWith(1, [{"id":"userId", "label":"User Name", "source":"users"}]);
  246. // Force the autocomplete to be initialized
  247. view._initAutoComplete($newCommentForm.find('.message'));
  248. // PhantomJS does not seem to handle typing in a contenteditable, so
  249. // some tricks are needed to show the autocomplete popover.
  250. //
  251. // Instead of sending key events to type "@u" the characters are
  252. // programatically set in the input field.
  253. $newCommentForm.find('.message').text('Mention to @u');
  254. // When focusing on the input field the caret is not guaranteed to
  255. // be at the end; instead of calling "focus()" on the input field
  256. // the caret is explicitly set at the end of the input field, that
  257. // is, after "@u".
  258. var range = document.createRange();
  259. range.selectNodeContents($newCommentForm.find('.message')[0]);
  260. range.collapse(false);
  261. var selection = window.getSelection();
  262. selection.removeAllRanges();
  263. selection.addRange(range);
  264. // As PhantomJS does not handle typing in a contenteditable the key
  265. // typed here is in practice ignored by At.js, but despite that it
  266. // will cause the popover to be shown.
  267. $newCommentForm.find('.message').trigger(new $.Event('keydown', {keyCode: 's'}));
  268. $newCommentForm.find('.message').trigger(new $.Event('keyup', {keyCode: 's'}));
  269. expect(autoCompleteStub.calledOnce).toEqual(true);
  270. var keydownEvent = new $.Event('keydown', {keyCode: 13});
  271. $newCommentForm.find('.message').trigger(keydownEvent);
  272. expect(createStub.calledOnce).toEqual(false);
  273. expect($newCommentForm.find('.message').html()).toContain('Mention to <span');
  274. expect($newCommentForm.find('.message').html()).toContain('<span class="avatar"');
  275. expect($newCommentForm.find('.message').html()).toContain('<strong>User Name</strong>');
  276. expect($newCommentForm.find('.message').text()).not.toContain('@');
  277. // In this case the default behaviour is prevented by the
  278. // "onKeydown" event handler of At.js.
  279. expect(keydownEvent.isDefaultPrevented()).toEqual(true);
  280. });
  281. it('creates a new line when typing shift+enter', function() {
  282. $newCommentForm.find('.message').text('New message');
  283. var keydownEvent = new $.Event('keydown', {keyCode: 13, shiftKey: true});
  284. $newCommentForm.find('.message').trigger(keydownEvent);
  285. expect(createStub.calledOnce).toEqual(false);
  286. // PhantomJS does not seem to handle typing in a contenteditable, so
  287. // instead of looking for a new line the best that can be done is
  288. // checking that the default behaviour would have been executed.
  289. expect($newCommentForm.find('.message').text()).toContain('New message');
  290. expect(keydownEvent.isDefaultPrevented()).toEqual(false);
  291. });
  292. it('creates a new comment with mentions when clicking post button', function() {
  293. $newCommentForm.find('.message').text('New message @anotheruser');
  294. $newCommentForm.submit();
  295. var createStubExpectedData = {
  296. actorId: 'testuser',
  297. actorDisplayName: 'Test User',
  298. actorType: 'users',
  299. verb: 'comment',
  300. message: 'New message @anotheruser',
  301. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
  302. };
  303. expect(createStub.calledOnce).toEqual(true);
  304. expect(createStub.lastCall.args[0]).toEqual(createStubExpectedData);
  305. var model = new OCA.Comments.CommentModel(_.extend({id: 4}, createStubExpectedData));
  306. var fetchStub = sinon.stub(model, 'fetch');
  307. // simulate the fact that create adds the model to the collection
  308. view.collection.add(model, {at: 0});
  309. createStub.yieldTo('success', model);
  310. expect(fetchStub.calledOnce).toEqual(true);
  311. // simulate the fact that fetch sets the attribute
  312. model.set('mentions', {
  313. 0: {
  314. mentionDisplayName: "Another User",
  315. mentionId: "anotheruser",
  316. mentionTye: "user"
  317. }
  318. });
  319. fetchStub.yieldTo('success', model);
  320. // comment was added to the list
  321. var $comment = view.$el.find('.comment[data-id=4]');
  322. expect($comment.length).toEqual(1);
  323. var $message = $comment.find('.message');
  324. expect($message.html()).toContain('New message');
  325. expect($message.find('.avatar').length).toEqual(1);
  326. expect($message.find('.avatar[data-user=anotheruser]').length).toEqual(1);
  327. expect($message.find('.avatar[data-user=anotheruser] ~ strong').text()).toEqual('Another User');
  328. expect($message.find('.avatar[data-user=anotheruser] ~ .contactsmenu-popover').length).toEqual(1);
  329. });
  330. it('does not create a comment if the field is empty', function() {
  331. $newCommentForm.find('.message').val(' ');
  332. $newCommentForm.submit();
  333. expect(createStub.notCalled).toEqual(true);
  334. });
  335. it('does not create a comment if the field length is too large', function() {
  336. var bigMessage = '';
  337. for (var i = 0; i < view._commentMaxLength * 2; i++) {
  338. bigMessage += 'a';
  339. }
  340. $newCommentForm.find('.message').val(bigMessage);
  341. $newCommentForm.submit();
  342. expect(createStub.notCalled).toEqual(true);
  343. });
  344. describe('limit indicator', function() {
  345. var tooltipStub;
  346. var $message;
  347. var $submitButton;
  348. beforeEach(function() {
  349. tooltipStub = sinon.stub($.fn, 'tooltip');
  350. $message = $newCommentForm.find('.message');
  351. $submitButton = $newCommentForm.find('.submit');
  352. });
  353. afterEach(function() {
  354. tooltipStub.restore();
  355. });
  356. it('does not displays tooltip when limit is far away', function() {
  357. $message.val(createMessageWithLength(3));
  358. $message.trigger('change');
  359. expect(tooltipStub.calledWith('show')).toEqual(false);
  360. expect($submitButton.prop('disabled')).toEqual(false);
  361. expect($message.hasClass('error')).toEqual(false);
  362. });
  363. it('displays tooltip when limit is almost reached', function() {
  364. $message.text(createMessageWithLength(view._commentMaxLength - 2));
  365. $message.trigger('change');
  366. expect(tooltipStub.calledWith('show')).toEqual(true);
  367. expect($submitButton.prop('disabled')).toEqual(false);
  368. expect($message.hasClass('error')).toEqual(false);
  369. });
  370. it('displays tooltip and disabled button when limit is exceeded', function() {
  371. $message.text(createMessageWithLength(view._commentMaxLength + 2));
  372. $message.trigger('change');
  373. expect(tooltipStub.calledWith('show')).toEqual(true);
  374. expect($submitButton.prop('disabled')).toEqual(true);
  375. expect($message.hasClass('error')).toEqual(true);
  376. });
  377. });
  378. });
  379. describe('editing comments', function() {
  380. var saveStub;
  381. var fetchStub;
  382. var currentUserStub;
  383. beforeEach(function() {
  384. saveStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'save');
  385. fetchStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'fetch');
  386. currentUserStub = sinon.stub(OC, 'getCurrentUser');
  387. currentUserStub.returns({
  388. uid: 'testuser',
  389. displayName: 'Test User'
  390. });
  391. view.collection.add({
  392. id: 1,
  393. actorId: 'testuser',
  394. actorDisplayName: 'Test User',
  395. actorType: 'users',
  396. verb: 'comment',
  397. message: 'New message',
  398. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
  399. });
  400. view.collection.add({
  401. id: 2,
  402. actorId: 'anotheruser',
  403. actorDisplayName: 'Another User',
  404. actorType: 'users',
  405. verb: 'comment',
  406. message: 'New message from another user',
  407. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
  408. });
  409. view.collection.add({
  410. id: 3,
  411. actorId: 'testuser',
  412. actorDisplayName: 'Test User',
  413. actorType: 'users',
  414. verb: 'comment',
  415. message: 'Hail to thee, @macbeth. Yours faithfully, @banquo',
  416. creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
  417. mentions: {
  418. 0: {
  419. mentionDisplayName: "Thane of Cawdor",
  420. mentionId: "macbeth",
  421. mentionTye: "user"
  422. },
  423. 1: {
  424. mentionDisplayName: "Lord Banquo",
  425. mentionId: "banquo",
  426. mentionTye: "user"
  427. }
  428. }
  429. });
  430. });
  431. afterEach(function() {
  432. saveStub.restore();
  433. fetchStub.restore();
  434. currentUserStub.restore();
  435. });
  436. it('shows edit link for owner comments', function() {
  437. var $comment = view.$el.find('.comment[data-id=1]');
  438. expect($comment.length).toEqual(1);
  439. $comment.find('.action.more').trigger('click');
  440. expect($comment.find('.action.edit').length).toEqual(1);
  441. });
  442. it('does not show edit link for other user\'s comments', function() {
  443. var $comment = view.$el.find('.comment[data-id=2]');
  444. expect($comment.length).toEqual(1);
  445. $comment.find('.action.more').trigger('click');
  446. expect($comment.find('.action.edit').length).toEqual(0);
  447. });
  448. it('shows edit form when clicking edit', function() {
  449. var $comment = view.$el.find('.comment[data-id=1]');
  450. $comment.find('.action.more').trigger('click');
  451. $comment.find('.action.edit').trigger('click');
  452. expect($comment.hasClass('hidden')).toEqual(true);
  453. var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
  454. expect($formRow.length).toEqual(1);
  455. });
  456. it('saves message and updates comment item when clicking save', function() {
  457. var $comment = view.$el.find('.comment[data-id=1]');
  458. $comment.find('.action.more').trigger('click');
  459. $comment.find('.action.edit').trigger('click');
  460. var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
  461. expect($formRow.length).toEqual(1);
  462. $formRow.find('div.message').text('modified message');
  463. $formRow.find('form').submit();
  464. expect(saveStub.calledOnce).toEqual(true);
  465. expect(saveStub.lastCall.args[0]).toEqual({
  466. message: 'modified message'
  467. });
  468. var model = view.collection.get(1);
  469. // simulate the fact that save sets the attribute
  470. model.set('message', 'modified\nmessage');
  471. saveStub.yieldTo('success', model);
  472. view.collection.get(model);
  473. expect(fetchStub.called).toEqual(true);
  474. fetchStub.yieldTo('success', model);
  475. // original comment element is visible again
  476. expect($comment.hasClass('hidden')).toEqual(false);
  477. // and its message was updated
  478. expect($comment.find('.message').html()).toEqual('modified<br>message');
  479. // form row is gone
  480. $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
  481. expect($formRow.length).toEqual(0);
  482. });
  483. it('saves message and updates comment item with mentions when clicking save', function() {
  484. var $comment = view.$el.find('.comment[data-id=3]');
  485. $comment.find('.action.more').trigger('click');
  486. $comment.find('.action.edit').trigger('click');
  487. var $formRow = view.$el.find('.newCommentRow.comment[data-id=3]');
  488. expect($formRow.length).toEqual(1);
  489. $formRow.find('div.message').text('modified\nmessage @anotheruser');
  490. $formRow.find('form').submit();
  491. expect(saveStub.calledOnce).toEqual(true);
  492. expect(saveStub.lastCall.args[0]).toEqual({
  493. message: 'modified\nmessage @anotheruser'
  494. });
  495. var model = view.collection.get(3);
  496. // simulate the fact that save sets the attribute
  497. model.set('message', 'modified\nmessage @anotheruser');
  498. saveStub.yieldTo('success', model);
  499. expect(fetchStub.called).toEqual(true);
  500. // simulate the fact that fetch sets the attribute
  501. model.set('mentions', {
  502. 0: {
  503. mentionDisplayName: "Another User",
  504. mentionId: "anotheruser",
  505. mentionTye: "user"
  506. }
  507. });
  508. fetchStub.yieldTo('success', model);
  509. // original comment element is visible again
  510. expect($comment.hasClass('hidden')).toEqual(false);
  511. // and its message was updated
  512. var $message = $comment.find('.message');
  513. expect($message.html()).toContain('modified<br>message');
  514. expect($message.find('.avatar').length).toEqual(1);
  515. expect($message.find('.avatar[data-user=anotheruser]').length).toEqual(1);
  516. expect($message.find('.avatar[data-user=anotheruser] ~ strong').text()).toEqual('Another User');
  517. expect($message.find('.avatar[data-user=anotheruser] ~ .contactsmenu-popover').length).toEqual(1);
  518. // form row is gone
  519. $formRow = view.$el.find('.newCommentRow.comment[data-id=3]');
  520. expect($formRow.length).toEqual(0);
  521. });
  522. it('restores original comment when cancelling', function() {
  523. var $comment = view.$el.find('.comment[data-id=1]');
  524. $comment.find('.action.more').trigger('click');
  525. $comment.find('.action.edit').trigger('click');
  526. var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
  527. expect($formRow.length).toEqual(1);
  528. $formRow.find('textarea').val('modified\nmessage');
  529. $formRow.find('.cancel').trigger('click');
  530. expect(saveStub.notCalled).toEqual(true);
  531. // original comment element is visible again
  532. expect($comment.hasClass('hidden')).toEqual(false);
  533. // and its message was not updated
  534. expect($comment.find('.message').html()).toEqual('New message');
  535. // form row is gone
  536. $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
  537. expect($formRow.length).toEqual(0);
  538. });
  539. it('destroys model when clicking delete', function() {
  540. var destroyStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'destroy');
  541. var $comment = view.$el.find('.comment[data-id=1]');
  542. $comment.find('.action.more').trigger('click');
  543. $comment.find('.action.delete').trigger('click');
  544. expect(destroyStub.calledOnce).toEqual(true);
  545. expect(destroyStub.thisValues[0].id).toEqual(1);
  546. destroyStub.yieldTo('success');
  547. // original comment element is gone
  548. $comment = view.$el.find('.comment[data-id=1]');
  549. expect($comment.length).toEqual(0);
  550. destroyStub.restore();
  551. });
  552. it('does not submit comment if the field is empty', function() {
  553. var $comment = view.$el.find('.comment[data-id=1]');
  554. $comment.find('.action.edit').trigger('click');
  555. $comment.find('.message').val(' ');
  556. $comment.find('form').submit();
  557. expect(saveStub.notCalled).toEqual(true);
  558. });
  559. it('does not submit comment if the field length is too large', function() {
  560. var $comment = view.$el.find('.comment[data-id=1]');
  561. $comment.find('.action.edit').trigger('click');
  562. $comment.find('.message').val(createMessageWithLength(view._commentMaxLength * 2));
  563. $comment.find('form').submit();
  564. expect(saveStub.notCalled).toEqual(true);
  565. });
  566. });
  567. describe('read marker', function() {
  568. var updateMarkerStub;
  569. beforeEach(function() {
  570. updateMarkerStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'updateReadMarker');
  571. });
  572. afterEach(function() {
  573. updateMarkerStub.restore();
  574. });
  575. it('resets the read marker after REPORT', function() {
  576. testComments[0].set('isUnread', true, {silent: true});
  577. testComments[1].set('isUnread', true, {silent: true});
  578. view.collection.set(testComments);
  579. view.collection.trigger('sync', 'REPORT');
  580. expect(updateMarkerStub.calledOnce).toEqual(true);
  581. expect(updateMarkerStub.lastCall.args[0]).toBeFalsy();
  582. });
  583. it('does not reset the read marker if there was no unread comments', function() {
  584. view.collection.set(testComments);
  585. view.collection.trigger('sync', 'REPORT');
  586. expect(updateMarkerStub.notCalled).toEqual(true);
  587. });
  588. it('does not reset the read marker when posting comments', function() {
  589. testComments[0].set('isUnread', true, {silent: true});
  590. testComments[1].set('isUnread', true, {silent: true});
  591. view.collection.set(testComments);
  592. view.collection.trigger('sync', 'POST');
  593. expect(updateMarkerStub.notCalled).toEqual(true);
  594. });
  595. });
  596. });