diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 09f0ea37103..b0573510ff9 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1809,9 +1809,11 @@ export default class Notes { } } + $closeBtn.text($closeBtn.data('originalText')); + /* eslint-disable promise/catch-or-return */ // Make request to submit comment on server - axios + return axios .post(`${formAction}?html=true`, formData) .then(res => { const note = res.data; @@ -1928,8 +1930,6 @@ export default class Notes { this.reenableTargetFormSubmitButton(e); this.addNoteError($form); }); - - return $closeBtn.text($closeBtn.data('originalText')); } /** diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 1858d6b6474..ec56ab0e2f0 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -16,15 +16,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; window.gl = window.gl || {}; gl.utils = gl.utils || {}; - const htmlEscape = (comment) => { - const escapedString = comment.replace(/["&'<>]/g, (a) => { + const htmlEscape = comment => { + const escapedString = comment.replace(/["&'<>]/g, a => { const escapedToken = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', - '`': '`' + '`': '`', }[a]; return escapedToken; @@ -39,7 +39,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; var commentsTemplate = 'merge_requests/merge_request_with_comment.html.raw'; preloadFixtures(commentsTemplate); - beforeEach(function () { + beforeEach(function() { loadFixtures(commentsTemplate); gl.utils.disableButtonIfEmptyField = _.noop; window.project_uploads_path = 'http://test.host/uploads'; @@ -51,6 +51,17 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $('body').removeAttr('data-page'); }); + describe('addBinding', () => { + it('calls postComment when comment button is clicked', () => { + spyOn(Notes.prototype, 'postComment'); + this.notes = new Notes('', []); + + $('.js-comment-button').click(); + + expect(Notes.prototype.postComment).toHaveBeenCalled(); + }); + }); + describe('task lists', function() { let mock; @@ -58,7 +69,13 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; spyOn(axios, 'patch').and.callThrough(); mock = new MockAdapter(axios); - mock.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`).reply(200, {}); + mock + .onPatch( + `${ + gl.TEST_HOST + }/frontend-fixtures/merge-requests-project/merge_requests/1.json`, + ) + .reply(200, {}); $('.js-comment-button').on('click', function(e) { e.preventDefault(); @@ -73,18 +90,27 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('modifies the Markdown field', function() { const changeEvent = document.createEvent('HTMLEvents'); changeEvent.initEvent('change', true, true); - $('input[type=checkbox]').attr('checked', true)[1].dispatchEvent(changeEvent); + $('input[type=checkbox]') + .attr('checked', true)[1] + .dispatchEvent(changeEvent); - expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item'); + expect($('.js-task-list-field.original-task-list').val()).toBe( + '- [x] Task List Item', + ); }); it('submits an ajax request on tasklist:changed', function(done) { $('.js-task-list-container').trigger('tasklist:changed'); setTimeout(() => { - expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, { - note: { note: '' }, - }); + expect(axios.patch).toHaveBeenCalledWith( + `${ + gl.TEST_HOST + }/frontend-fixtures/merge-requests-project/merge_requests/1.json`, + { + note: { note: '' }, + }, + ); done(); }); }); @@ -100,10 +126,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; spyOn(this.notes, 'renderNote').and.stub(); $(textarea).data('autosave', { - reset: function() {} + reset: function() {}, }); - $('.js-comment-button').on('click', (e) => { + $('.js-comment-button').on('click', e => { const $form = $(this); e.preventDefault(); this.notes.addNote($form); @@ -149,7 +175,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
${sampleComment}
`, note: sampleComment, - valid: true + valid: true, }; $form = $('form.js-main-target-form'); $notesContainer = $('ul.main-notes-list'); @@ -163,7 +189,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; mock.restore(); }); - it('updates note and resets edit form', (done) => { + it('updates note and resets edit form', done => { spyOn(this.notes, 'revertNoteEditForm'); spyOn(this.notes, 'setupNewNote'); @@ -175,7 +201,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; updatedNote.note = 'bar'; this.notes.updateNote(updatedNote, $targetNote); - expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote); + expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith( + $targetNote, + ); expect(this.notes.setupNewNote).toHaveBeenCalled(); done(); @@ -231,17 +259,14 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; note: 'heya', html: '
heya
', }; - $notesList = jasmine.createSpyObj('$notesList', [ - 'find', - 'append', - ]); + $notesList = jasmine.createSpyObj('$notesList', ['find', 'append']); notes = jasmine.createSpyObj('notes', [ 'setupNewNote', 'refresh', 'collapseLongCommitList', 'updateNotesCount', - 'putConflictEditWarningInPlace' + 'putConflictEditWarningInPlace', ]); notes.taskList = jasmine.createSpyObj('tasklist', ['init']); notes.note_ids = []; @@ -258,7 +283,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; Notes.isNewNote.and.returnValue(true); Notes.prototype.renderNote.call(notes, note, null, $notesList); - expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList); + expect(Notes.animateAppendNote).toHaveBeenCalledWith( + note.html, + $notesList, + ); }); }); @@ -273,7 +301,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; Notes.prototype.renderNote.call(notes, note, null, $notesList); - expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note); + expect(Notes.animateUpdateNote).toHaveBeenCalledWith( + note.html, + $note, + ); expect(notes.setupNewNote).toHaveBeenCalledWith($newNote); }); @@ -301,7 +332,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $notesList.find.and.returnValue($note); Notes.prototype.renderNote.call(notes, note, null, $notesList); - expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note); + expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith( + note, + $note, + ); }); }); }); @@ -311,11 +345,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should consider same note text as the same', () => { const result = Notes.isUpdatedNote( { - note: 'initial' + note: 'initial', }, $(`
initial
-
`) + `), ); expect(result).toEqual(false); @@ -324,11 +358,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should consider same note with trailing newline as the same', () => { const result = Notes.isUpdatedNote( { - note: 'initial\n' + note: 'initial\n', }, $(`
initial\n
-
`) + `), ); expect(result).toEqual(false); @@ -337,11 +371,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should consider different notes as different', () => { const result = Notes.isUpdatedNote( { - note: 'foo' + note: 'foo', }, $(`
bar
-
`) + `), ); expect(result).toEqual(true); @@ -397,7 +431,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should call Notes.animateAppendNote', () => { Notes.prototype.renderDiscussionNote.call(notes, note, $form); - expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.discussion_html, $('.main-notes-list')); + expect(Notes.animateAppendNote).toHaveBeenCalledWith( + note.discussion_html, + $('.main-notes-list'), + ); }); it('should append to row selected with line_code', () => { @@ -428,7 +465,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); it('should call Notes.animateAppendNote', () => { - expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer); + expect(Notes.animateAppendNote).toHaveBeenCalledWith( + note.html, + discussionContainer, + ); }); }); }); @@ -461,9 +501,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; beforeEach(() => { noteHTML = '
'; - $note = jasmine.createSpyObj('$note', [ - 'replaceWith' - ]); + $note = jasmine.createSpyObj('$note', ['replaceWith']); $updatedNote = Notes.animateUpdateNote(noteHTML, $note); }); @@ -501,7 +539,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
${sampleComment}
`, note: sampleComment, - valid: true + valid: true, }; let $form; let $notesContainer; @@ -534,10 +572,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; mockNotesPost(); $('.js-comment-button').click(); - expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true); + expect($notesContainer.find('.note.being-posted').length > 0).toEqual( + true, + ); }); - it('should remove placeholder note when new comment is done posting', (done) => { + it('should remove placeholder note when new comment is done posting', done => { mockNotesPost(); $('.js-comment-button').click(); @@ -549,33 +589,44 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); }); - it('should disable the submit button when comment button is clicked', (done) => { - expect($form.find('.js-comment-submit-button').is(':disabled')).toEqual(false); + describe('postComment', () => { + it('disables the submit button', done => { + const $submitButton = $form.find('.js-comment-submit-button'); + expect($submitButton).not.toBeDisabled(); + const dummyEvent = { + preventDefault() {}, + target: $submitButton, + }; + mock.onPost(NOTES_POST_PATH).replyOnce(() => { + expect($submitButton).toBeDisabled(); + return [200, note]; + }); - mockNotesPost(); - $('.js-comment-button').click(); - expect($form.find('.js-comment-submit-button').is(':disabled')).toEqual(true); - - setTimeout(() => { - expect($form.find('.js-comment-submit-button').is(':disabled')).toEqual(false); - - done(); + this.notes + .postComment(dummyEvent) + .then(() => { + expect($submitButton).not.toBeDisabled(); + }) + .then(done) + .catch(done.fail); }); }); - it('should show actual note element when new comment is done posting', (done) => { + it('should show actual note element when new comment is done posting', done => { mockNotesPost(); $('.js-comment-button').click(); setTimeout(() => { - expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true); + expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual( + true, + ); done(); }); }); - it('should reset Form when new comment is done posting', (done) => { + it('should reset Form when new comment is done posting', done => { mockNotesPost(); $('.js-comment-button').click(); @@ -587,19 +638,24 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); }); - it('should show flash error message when new comment failed to be posted', (done) => { + it('should show flash error message when new comment failed to be posted', done => { mockNotesPostError(); $('.js-comment-button').click(); setTimeout(() => { - expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true); + expect( + $notesContainer + .parent() + .find('.flash-container .flash-text') + .is(':visible'), + ).toEqual(true); done(); }); }); - it('should show flash error message when comment failed to be updated', (done) => { + it('should show flash error message when comment failed to be updated', done => { mockNotesPost(); $('.js-comment-button').click(); @@ -620,7 +676,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; .then(() => { const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`); expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals - expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original + expect( + $updatedNoteEl + .find('.note-text') + .text() + .trim(), + ).toEqual(sampleComment); // See if comment reverted back to original expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown done(); @@ -634,12 +695,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const note = { commands_changes: { assignee_id: 1, - emoji_award: '100' + emoji_award: '100', }, errors: { - commands_only: ['Commands applied'] + commands_only: ['Commands applied'], }, - valid: false + valid: false, }; let $form; let $notesContainer; @@ -654,12 +715,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; window.gon.current_user_fullname = 'Administrator'; gl.awardsHandler = { addAwardToEmojiBar: () => {}, - scrollToAwards: () => {} + scrollToAwards: () => {}, }; gl.GfmAutoComplete = { dataSources: { - commands: '/root/test-project/autocomplete_sources/commands' - } + commands: '/root/test-project/autocomplete_sources/commands', + }, }; $form = $('form.js-main-target-form'); $notesContainer = $('ul.main-notes-list'); @@ -670,14 +731,18 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; mock.restore(); }); - it('should remove slash command placeholder when comment with slash commands is done posting', (done) => { + it('should remove slash command placeholder when comment with slash commands is done posting', done => { spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough(); $('.js-comment-button').click(); - expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown + expect( + $notesContainer.find('.system-note.being-posted').length, + ).toEqual(1); // Placeholder shown setTimeout(() => { - expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed + expect( + $notesContainer.find('.system-note.being-posted').length, + ).toEqual(0); // Placeholder removed done(); }); }); @@ -692,7 +757,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
${sampleComment}
`, note: sampleComment, - valid: true + valid: true, }; let $form; let $notesContainer; @@ -714,7 +779,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; mock.restore(); }); - it('should not render a script tag', (done) => { + it('should not render a script tag', done => { $('.js-comment-button').click(); setTimeout(() => { @@ -723,8 +788,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $noteEl.find('textarea.js-note-text').html(updatedComment); $noteEl.find('.js-comment-save-button').click(); - const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container'); - expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(''); + const $updatedNoteEl = $notesContainer + .find(`#note_${note.id}`) + .find('.js-task-list-container'); + expect( + $updatedNoteEl + .find('.note-text') + .text() + .trim(), + ).toEqual(''); done(); }); @@ -744,7 +816,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should return form metadata object from form reference', () => { $form.find('textarea.js-note-text').val(sampleComment); - const { formData, formContent, formAction } = this.notes.getFormData($form); + const { formData, formContent, formAction } = this.notes.getFormData( + $form, + ); expect(formData.indexOf(sampleComment) > -1).toBe(true); expect(formContent).toEqual(sampleComment); @@ -760,7 +834,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const { formContent } = this.notes.getFormData($form); expect(_.escape).toHaveBeenCalledWith(sampleComment); - expect(formContent).toEqual('<script>alert("Boom!");</script>'); + expect(formContent).toEqual( + '<script>alert("Boom!");</script>', + ); }); }); @@ -770,7 +846,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); it('should return true when comment begins with a quick action', () => { - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; + const sampleComment = + '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; const hasQuickActions = this.notes.hasQuickActions(sampleComment); expect(hasQuickActions).toBeTruthy(); @@ -794,7 +871,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('stripQuickActions', () => { it('should strip quick actions from the comment which begins with a quick action', () => { this.notes = new Notes(); - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; + const sampleComment = + '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe(''); @@ -802,7 +880,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should strip quick actions from the comment but leaves plain comment if it is present', () => { this.notes = new Notes(); - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this'; + const sampleComment = + '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this'; const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe('Merging this'); @@ -810,7 +889,8 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should NOT strip string that has slashes within', () => { this.notes = new Notes(); - const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1'; + const sampleComment = + 'http://127.0.0.1:3000/root/gitlab-shell/issues/1'; const stripedComment = this.notes.stripQuickActions(sampleComment); expect(stripedComment).toBe(sampleComment); @@ -821,7 +901,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const availableQuickActions = [ { name: 'close', description: 'Close this issue', params: [] }, { name: 'title', description: 'Change title', params: [{}] }, - { name: 'estimate', description: 'Set time estimate', params: [{}] } + { name: 'estimate', description: 'Set time estimate', params: [{}] }, ]; beforeEach(() => { @@ -830,17 +910,29 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; it('should return executing quick action description when note has single quick action', () => { const sampleComment = '/close'; - expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying command to close this issue'); + expect( + this.notes.getQuickActionDescription( + sampleComment, + availableQuickActions, + ), + ).toBe('Applying command to close this issue'); }); it('should return generic multiple quick action description when note has multiple quick actions', () => { const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands'); + expect( + this.notes.getQuickActionDescription( + sampleComment, + availableQuickActions, + ), + ).toBe('Applying multiple commands'); }); it('should return generic quick action description when available quick actions list is not populated', () => { const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command'); + expect(this.notes.getQuickActionDescription(sampleComment)).toBe( + 'Applying command', + ); }); }); @@ -870,14 +962,35 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; expect($tempNote.attr('id')).toEqual(uniqueId); expect($tempNote.hasClass('being-posted')).toBeTruthy(); expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); - $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() { - expect($(this).attr('href')).toEqual(`/${currentUsername}`); - }); - expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy(); - expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual(currentUserFullname); - expect($tempNoteHeader.find('.note-headline-light').text().trim()).toEqual(`@${currentUsername}`); - expect($tempNote.find('.note-body .note-text p').text().trim()).toEqual(sampleComment); + $tempNote + .find('.timeline-icon > a, .note-header-info > a') + .each(function() { + expect($(this).attr('href')).toEqual(`/${currentUsername}`); + }); + expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual( + currentUserAvatar, + ); + expect( + $tempNote.find('.timeline-content').hasClass('discussion'), + ).toBeFalsy(); + expect( + $tempNoteHeader + .find('.hidden-xs') + .text() + .trim(), + ).toEqual(currentUserFullname); + expect( + $tempNoteHeader + .find('.note-headline-light') + .text() + .trim(), + ).toEqual(`@${currentUsername}`); + expect( + $tempNote + .find('.note-body .note-text p') + .text() + .trim(), + ).toEqual(sampleComment); }); it('should return constructed placeholder element for discussion note based on form contents', () => { @@ -886,11 +999,13 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; uniqueId, isDiscussionNote: true, currentUsername, - currentUserFullname + currentUserFullname, }); expect($tempNote.prop('nodeName')).toEqual('LI'); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy(); + expect( + $tempNote.find('.timeline-content').hasClass('discussion'), + ).toBeTruthy(); }); it('should return a escaped user name', () => { @@ -904,7 +1019,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; currentUserAvatar, }); const $tempNoteHeader = $tempNote.find('.note-header'); - expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual('Foo <script>alert("XSS")</script>'); + expect( + $tempNoteHeader + .find('.hidden-xs') + .text() + .trim(), + ).toEqual('Foo <script>alert("XSS")</script>'); }); }); @@ -927,7 +1047,12 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; expect($tempNote.attr('id')).toEqual(uniqueId); expect($tempNote.hasClass('being-posted')).toBeTruthy(); expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); - expect($tempNote.find('.timeline-content i').text().trim()).toEqual(sampleCommandDescription); + expect( + $tempNote + .find('.timeline-content i') + .text() + .trim(), + ).toEqual(sampleCommandDescription); }); }); @@ -937,7 +1062,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); it('shows a flash message', () => { - this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); + this.notes.addFlash( + 'Error message', + FLASH_TYPE_ALERT, + this.notes.parentTimeline.get(0), + ); expect($('.flash-alert').is(':visible')).toBeTruthy(); }); @@ -950,7 +1079,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); it('hides visible flash message', () => { - this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); + this.notes.addFlash( + 'Error message 1', + FLASH_TYPE_ALERT, + this.notes.parentTimeline.get(0), + ); this.notes.clearFlash(); @@ -958,4 +1091,4 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; }); }); }); -}).call(window); +}.call(window));