diff --git a/Gruntfile.js b/Gruntfile.js index 7aca0619c2..dd5022cd1d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -494,6 +494,7 @@ var path = require('path'), 'core/built/scripts/vendor-ember.js': [ 'bower_components/loader.js/loader.js', 'bower_components/jquery/dist/jquery.js', + 'bower_components/lodash/dist/lodash.js', 'bower_components/handlebars/handlebars.js', 'bower_components/ember/ember.js', 'bower_components/ember-data/ember-data.js', @@ -604,11 +605,14 @@ var path = require('path'), // ### Spawn Casper.js // Custom test runner for our Casper.js functional tests // This really ought to be refactored into a separate grunt task module - grunt.registerTask('spawnCasperJS', function () { + grunt.registerTask('spawnCasperJS', function (target) { + + target = _.contains(['client', 'clientold', 'frontend'], target) ? target + '/' : undefined; + var done = this.async(), options = ['host', 'noPort', 'port', 'email', 'password'], args = ['test'] - .concat(grunt.option('target') || ['admin/', 'frontend/']) + .concat(grunt.option('target') || target || ['client/', 'frontend/']) .concat(['--includes=base.js', '--log-level=debug', '--port=2369']); // Forward parameters from grunt to casperjs @@ -725,8 +729,8 @@ var path = require('path'), // // `NODE_ENV=testing grunt mochacli:section` // - // If you need to run an individual unit test file, you can do so, providing you have mocha installed globally by - // using a command in the form: + // If you need to run an individual unit test file, you can do so, providing you have mocha installed globally + // by using a command in the form: // // `NODE_ENV=testing mocha --timeout=15000 --ui=bdd --reporter=spec core/test/unit/config_spec.js` // diff --git a/core/client/.jshintrc b/core/client/.jshintrc index a7fb83b2e9..60ed532972 100644 --- a/core/client/.jshintrc +++ b/core/client/.jshintrc @@ -28,6 +28,7 @@ "DS": true, "$": true, "validator": true, - "ic": true + "ic": true, + "_": true } } diff --git a/core/client/components/gh-codemirror.js b/core/client/components/gh-codemirror.js index da1a9855b0..d1eb1eccc0 100644 --- a/core/client/components/gh-codemirror.js +++ b/core/client/components/gh-codemirror.js @@ -1,12 +1,13 @@ -/* global CodeMirror*/ +/*global CodeMirror */ + import MarkerManager from 'ghost/mixins/marker-manager'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; var onChangeHandler = function (cm, changeObj) { var line, component = cm.component, - checkLine = component.checkLine.bind(component), - checkMarkers = component.checkMarkers.bind(component); + checkLine = _.bind(component.checkLine, component), + checkMarkers = _.bind(component.checkMarkers, component); // fill array with a range of numbers for (line = changeObj.from.line; line < changeObj.from.line + changeObj.text.length; line += 1) { @@ -37,7 +38,7 @@ var Codemirror = Ember.TextArea.extend(MarkerManager, { }, afterRenderEvent: function () { - var initMarkers = this.initMarkers.bind(this); + var initMarkers = _.bind(this.initMarkers, this); this.initCodemirror(); this.codemirror.eachLine(initMarkers); diff --git a/core/client/components/gh-markdown.js b/core/client/components/gh-markdown.js index e83cc85a1b..c507cfc913 100644 --- a/core/client/components/gh-markdown.js +++ b/core/client/components/gh-markdown.js @@ -25,10 +25,10 @@ var Markdown = Ember.Component.extend({ filestorage: false }); - dropzones.on('uploadstart', this.sendAction.bind(this, 'uploadStarted')); - dropzones.on('uploadfailure', this.sendAction.bind(this, 'uploadFinished')); - dropzones.on('uploadsuccess', this.sendAction.bind(this, 'uploadFinished')); - dropzones.on('uploadsuccess', this.sendAction.bind(this, 'uploadSuccess')); + dropzones.on('uploadstart', _.bind(this.sendAction, this, 'uploadStarted')); + dropzones.on('uploadfailure', _.bind(this.sendAction, this, 'uploadFinished')); + dropzones.on('uploadsuccess', _.bind(this.sendAction, this, 'uploadFinished')); + dropzones.on('uploadsuccess', _.bind(this.sendAction, this, 'uploadSuccess')); }); }.observes('markdown') }); diff --git a/core/client/controllers/post-settings-menu.js b/core/client/controllers/post-settings-menu.js index 58831eac02..79c0ce7067 100644 --- a/core/client/controllers/post-settings-menu.js +++ b/core/client/controllers/post-settings-menu.js @@ -113,7 +113,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({ // if the candidate slug is the same as the existing slug except // for the incrementor then the existing slug should be used - if (Number.isInteger(check) && check > 0) { + if (_.isNumber(check) && check > 0) { if (slug === slugTokens.join('-') && serverSlug !== newSlug) { return; } diff --git a/core/test/functional/base.js b/core/test/functional/base.js index d40b81251e..99810720d7 100644 --- a/core/test/functional/base.js +++ b/core/test/functional/base.js @@ -27,9 +27,9 @@ var DEBUG = false, // TOGGLE THIS TO GET MORE SCREENSHOTS port = casper.cli.options.port || '2368', email = casper.cli.options.email || 'jbloggs@example.com', password = casper.cli.options.password || 'Sl1m3rson', - url = "http://" + host + (noPort ? '/' : ":" + port + "/"), + url = 'http://' + host + (noPort ? '/' : ':' + port + '/'), newUser = { - name: "Test User", + name: 'Test User', email: email, password: password }, @@ -42,12 +42,12 @@ var DEBUG = false, // TOGGLE THIS TO GET MORE SCREENSHOTS password: 'letmethrough' }, testPost = { - title: "Bacon ipsum dolor sit amet", - html: "I am a test post.\n#I have some small content" + title: 'Bacon ipsum dolor sit amet', + html: 'I am a test post.\n#I have some small content' }; casper.writeContentToCodeMirror = function (content) { - var lines = content.split("\n"); + var lines = content.split('\n'); casper.waitForSelector('.CodeMirror-wrap textarea', function onSuccess() { casper.each(lines, function (self, line) { @@ -62,13 +62,15 @@ casper.writeContentToCodeMirror = function (content) { }; casper.waitForOpaque = function (classname, then, timeout) { + timeout = timeout || casper.failOnTimeout(casper.test, 'waitForOpaque failed on ' + classname); + casper.waitFor(function checkOpaque() { var value = this.evaluate(function (element) { var target = document.querySelector(element); if (target === null) { return null; } - return window.getComputedStyle(target).getPropertyValue('opacity') === "1"; + return window.getComputedStyle(target).getPropertyValue('opacity') === '1'; }, classname); if (value !== true && value !== false) { casper.test.fail('Unable to find element: ' + classname); @@ -77,17 +79,77 @@ casper.waitForOpaque = function (classname, then, timeout) { }, then, timeout); }; +// ### Then Open And Wait For Page Load +// Always wait for the `#main` element as some indication that the ember app has loaded. +casper.thenOpenAndWaitForPageLoad = function (screen, then, timeout) { + then = then || function () {}; + timeout = timeout || casper.failOnTimeout(casper.test, 'Unable to load ' + screen); + + var screens = { + 'root': { + url: 'ghost/ember/', + selector: '#main-menu .content.active' + }, + 'content': { + url: 'ghost/ember/content/', + selector: '#main-menu .content.active' + }, + 'editor': { + url: 'ghost/ember/editor/', + selector: '#main-menu .editor.active' + }, + 'settings': { + url: 'ghost/ember/settings/', + selector: '.settings-content' + }, + 'settings.general': { + url: 'ghost/ember/settings/general', + selector: '.settings-content form#settings-general' + }, + 'settings.user': { + url: 'ghost/ember/settings/user', + selector: '.settings-content form.user-profile' + }, + 'signin': { + url: 'ghost/ember/signin/', + selector: '.button-save' + }, + 'signout': { + url: 'ghost/ember/signout/', + selector: '.button-save' + }, + 'signup': { + url: 'ghost/ember/signup/', + selector: '.button-save' + } + }; + + return casper.thenOpen(url + screens[screen].url).then(function () { + return casper.waitForSelector(screens[screen].selector, then, timeout, 15000); + }); +}; + casper.failOnTimeout = function (test, message) { return function onTimeout() { test.fail(message); }; }; +// ### Fill And Save +// With Ember in place, we don't want to submit forms, rather press the green button which always has a class of +// 'button-save'. This method handles that smoothly. +casper.fillAndSave = function (selector, data) { + casper.fill(selector, data, false); + casper.thenClick(selector + ' .button-save'); +}; + +// ## Echo Concise +// Does casper.echo but checks for the presence of the --concise flag casper.echoConcise = function (message, style) { if (!casper.cli.options.concise) { casper.echo(message, style); } -} +}; // ## Debugging // output all errors to the console @@ -99,24 +161,30 @@ casper.on('error', function (msg) { casper.echoConcise('GOT ERROR, ' + msg); }); -casper.on("page.error", function (msg) { - casper.echoConcise("GOT PAGE ERROR: " + msg, "ERROR"); +casper.on('page.error', function (msg) { + casper.echoConcise('GOT PAGE ERROR: ' + msg, 'ERROR'); }); casper.captureScreenshot = function (filename, debugOnly) { debugOnly = debugOnly !== false; // If we are in debug mode, OR debugOnly is false if (DEBUG || debugOnly === false) { - filename = filename || "casper_test_fail.png"; + filename = filename || 'casper_test_fail.png'; casper.then(function () { casper.capture(new Date().getTime() + '_' + filename); }); } }; + + // on failure, grab a screenshot -casper.test.on("fail", function captureFailure(test) { - casper.captureScreenshot(casper.test.filename || "casper_test_fail.png", false); +casper.test.on('fail', function captureFailure(test) { + casper.captureScreenshot(casper.test.filename || 'casper_test_fail.png', false); + casper.then(function () { + console.log(casper.getHTML()); + casper.exit(1); + }); }); var CasperTest = (function () { @@ -129,11 +197,7 @@ var CasperTest = (function () { casper.test.tearDown(function (done) { casper.then(_beforeDoneHandler); - casper.thenOpen(url + 'ghost\/signout/'); - - casper.waitForResource(/ghost\/sign/); - - casper.captureScreenshot('teardown.png'); + CasperTest.Routines.emberSignout.run(); casper.run(done); }); @@ -179,6 +243,48 @@ var CasperTest = (function () { } } + function emberBegin(testName, expect, suite, doNotAutoLogin) { + _beforeDoneHandler = _noop; + + var runTest = function (test) { + test.filename = testName.toLowerCase().replace(/ /g, '-').concat('.png'); + + casper.start('about:blank').viewport(1280, 1024); + + if (!doNotAutoLogin) { + // Only call register once for the lifetime of CasperTest + if (!_isUserRegistered) { + + CasperTest.Routines.emberSignout.run(); + CasperTest.Routines.emberSignup.run(); + + _isUserRegistered = true; + } + + /* Ensure we're logged out at the start of every test or we may get + unexpected failures. */ + CasperTest.Routines.emberSignout.run(); + CasperTest.Routines.emberSignin.run(); + } + + suite.call(casper, test); + + casper.run(function () { + test.done(); + }); + }; + + + if (typeof expect === 'function') { + doNotAutoLogin = suite; + suite = expect; + + casper.test.begin(testName, runTest); + } else { + casper.test.begin(testName, expect, runTest); + } + } + // Sets a handler to be invoked right before `test.done` is invoked function beforeDone(fn) { if (fn) { @@ -190,6 +296,7 @@ var CasperTest = (function () { return { begin: begin, + emberBegin: emberBegin, beforeDone: beforeDone }; @@ -198,7 +305,7 @@ var CasperTest = (function () { CasperTest.Routines = (function () { function register(test) { - casper.thenOpen(url + 'ghost/signup/').viewport(1280, 1024); + casper.thenOpen(url + 'ghost/signup/'); casper.waitForOpaque('.signup-box', function then() { this.fill('#signup', newUser, true); @@ -214,15 +321,43 @@ CasperTest.Routines = (function () { }, 2000); } + function emberSignup() { + casper.thenOpenAndWaitForPageLoad('signup', function then() { + casper.captureScreenshot('ember_signing_up1.png'); + + casper.waitForOpaque('.signup-box', function then() { + this.fillAndSave('#signup', newUser); + }); + + casper.captureScreenshot('ember_signing_up2.png'); + + casper.waitForSelectorTextChange('.notification-error', function onSuccess() { + var errorText = casper.evaluate(function () { + return document.querySelector('.notification-error').innerText; + }); + casper.echoConcise('It appears as though a user is already registered. Error text: ' + errorText); + }, function onTimeout() { + casper.echoConcise('It appears as though a user was not already registered.'); + }, 2000); + + casper.captureScreenshot('ember_signing_up3.png'); + + }); + } + function login(test) { casper.thenOpen(url + 'ghost/signin/'); casper.waitForResource(/ghost\/signin/); + casper.waitForSelector('.login-box', function () {}, function () { + console.log(casper.getHTML()); + }); + casper.waitForOpaque('.login-box', function then() { - casper.captureScreenshot("got_sign_in.png"); - this.fill("#login", user, true); - casper.captureScreenshot("filled_sign_in.png"); + casper.captureScreenshot('got_sign_in.png'); + this.fill('#login', user, true); + casper.captureScreenshot('filled_sign_in.png'); }); casper.waitForResource(/ghost\/$/).then(function () { @@ -230,17 +365,41 @@ CasperTest.Routines = (function () { }); } - function logout(test) { - casper.thenOpen(url + 'ghost\/signout/'); + function emberSignin() { + casper.thenOpenAndWaitForPageLoad('signin', function then() { - casper.captureScreenshot("logging_out.png"); + casper.waitForOpaque('.login-box', function then() { + casper.captureScreenshot('ember_signing_in.png'); + this.fillAndSave('#login', user); + casper.captureScreenshot('ember_signing_in2.png'); + }); + + casper.waitForResource(/posts\/\?status=all&staticPages=all/, function then() { + casper.captureScreenshot('ember_signing_in3.png'); + }, function timeout() { + casper.test.fail('Unable to signin and load admin panel'); + }); + }); + } + + function logout(test) { + casper.thenOpen(url + 'ghost/signout/'); + + casper.captureScreenshot('logging_out.png'); // Wait for signin or signup casper.waitForResource(/ghost\/sign/); } + function emberSignout() { + casper.thenOpenAndWaitForPageLoad('signout', function then() { + casper.captureScreenshot('ember_signing_out.png'); + }); + } + + // This will need switching over to ember once settings general is working properly. function togglePermalinks(state) { - casper.thenOpen(url + "ghost/settings/general"); + casper.thenOpen(url + 'ghost/settings/general'); casper.waitForResource(/ghost\/settings\/general/); @@ -253,15 +412,47 @@ CasperTest.Routines = (function () { casper.thenClick('#permalinks'); casper.thenClick('.button-save'); - casper.captureScreenshot("saving.png"); + casper.captureScreenshot('saving.png'); casper.waitForSelector('.notification-success', function () { - casper.captureScreenshot("saved.png"); + casper.captureScreenshot('saved.png'); }); } }); } + function createTestPost(publish) { + casper.thenOpenAndWaitForPageLoad('editor', function createTestPost() { + casper.sendKeys('#entry-title', testPost.title); + casper.writeContentToCodeMirror(testPost.html); + casper.sendKeys('#entry-tags input.tag-input', 'TestTag'); + casper.sendKeys('#entry-tags input.tag-input', casper.page.event.key.Enter); + }); + + casper.waitForSelectorTextChange('.entry-preview .rendered-markdown'); + + if (publish) { + // Open the publish options menu; + casper.thenClick('.js-publish-splitbutton .options.up'); + + casper.waitForOpaque('.js-publish-splitbutton .open'); + + // Select the publish post button + casper.thenClick('.js-publish-splitbutton li:first-child a'); + + casper.waitForSelectorTextChange('.js-publish-button', function onSuccess() { + casper.thenClick('.js-publish-button'); + }); + } else { + casper.thenClick('.js-publish-button'); + } + + // **Note:** This should include tags on all post requests! Uncomment and replace lines below with this when fixed. + // casper.waitForResource(/posts\/\?include=tags$/); + + casper.waitForResource(/posts\/$/); + } + function _createRunner(fn) { fn.run = function run(test) { var routine = this; @@ -278,7 +469,11 @@ CasperTest.Routines = (function () { register: _createRunner(register), login: _createRunner(login), logout: _createRunner(logout), - togglePermalinks: _createRunner(togglePermalinks) + togglePermalinks: _createRunner(togglePermalinks), + emberSignup: _createRunner(emberSignup), + emberSignin: _createRunner(emberSignin), + emberSignout: _createRunner(emberSignout), + createTestPost: _createRunner(createTestPost) }; }()); \ No newline at end of file diff --git a/core/test/functional/client/app_test.js b/core/test/functional/client/app_test.js new file mode 100644 index 0000000000..fcb91c7abf --- /dev/null +++ b/core/test/functional/client/app_test.js @@ -0,0 +1,67 @@ +// # App Test +// Tests that the general layout & functionality of global admin components is correct + +/*globals casper, __utils__, url, testPost, newUser */ + +CasperTest.emberBegin('Admin navigation bar is correct', 28, function suite(test) { + casper.thenOpenAndWaitForPageLoad('root', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, 'Landed on the correct URL'); + }); + + casper.then(function testNavItems() { + var logoHref = this.getElementAttribute('a.ghost-logo', 'href'), + contentHref = this.getElementAttribute('#main-menu li.content a', 'href'), + editorHref = this.getElementAttribute('#main-menu li.editor a', 'href'), + settingsHref = this.getElementAttribute('#main-menu li.settings a', 'href'); + + // Logo + test.assertExists('a.ghost-logo', 'Ghost logo home page link exists'); + test.assertEquals(logoHref, '/', 'Ghost logo href is correct'); + + // Content + test.assertExists('#main-menu li.content a', 'Content nav item exists'); + test.assertSelectorHasText('#main-menu li.content a', 'Content', 'Content nav item has correct text'); + test.assertEquals(contentHref, '/ghost/ember/', 'Content href is correct'); + test.assertExists('#main-menu li.content.active', 'Content nav item is not marked active'); + + // Editor + test.assertExists('#main-menu li.editor a', 'Editor nav item exists'); + test.assertSelectorHasText('#main-menu li.editor a', 'New Post', 'Editor nav item has correct text'); + test.assertEquals(editorHref, '/ghost/ember/editor/', 'Editor href is correct'); + test.assertDoesntExist('#main-menu li.editor.active', 'Editor nav item is not marked active'); + + // Settings + test.assertExists('#main-menu li.settings a', 'Settings nav item exists'); + test.assertSelectorHasText('#main-menu li.settings a', 'Settings', 'Settings nav item has correct text'); + test.assertEquals(settingsHref, '/ghost/ember/settings/', 'Settings href is correct'); + test.assertDoesntExist('#main-menu li.settings.active', 'Settings nav item is marked active'); + }); + + casper.then(function testUserMenuNotVisible() { + test.assertExists('#usermenu', 'User menu nav item exists'); + test.assertNotVisible('#usermenu ul.overlay', 'User menu should not be visible'); + }); + + casper.thenClick('#usermenu a'); + casper.waitForSelector('#usermenu ul.overlay', function then() { + var profileHref = this.getElementAttribute('#usermenu li.usermenu-profile a', 'href'), + helpHref = this.getElementAttribute('#usermenu li.usermenu-help a', 'href'), + signoutHref = this.getElementAttribute('#usermenu li.usermenu-signout a', 'href'); + + test.assertVisible('#usermenu ul.overlay', 'User menu should be visible'); + + test.assertExists('#usermenu li.usermenu-profile a', 'Profile menu item exists'); + test.assertSelectorHasText('#usermenu li.usermenu-profile a', 'Your Profile', + 'Profile menu item has correct text'); + test.assertEquals(profileHref, '/ghost/ember/settings/user/', 'Profile href is correct'); + + test.assertExists('#usermenu li.usermenu-help a', 'Help menu item exists'); + test.assertSelectorHasText('#usermenu li.usermenu-help a', 'Help / Support', 'Help menu item has correct text'); + test.assertEquals(helpHref, 'http://support.ghost.org/', 'Help href is correct'); + + test.assertExists('#usermenu li.usermenu-signout a', 'Sign Out menu item exists'); + test.assertSelectorHasText('#usermenu li.usermenu-signout a', 'Sign Out', 'Signout menu item has correct text'); + test.assertEquals(signoutHref, '/ghost/ember/signout/', 'Sign Out href is correct'); + }, casper.failOnTimeout(test, 'WaitForSelector #usermenu ul.overlay failed')); +}); diff --git a/core/test/functional/client/content_test.js b/core/test/functional/client/content_test.js new file mode 100644 index 0000000000..7b980ae748 --- /dev/null +++ b/core/test/functional/client/content_test.js @@ -0,0 +1,321 @@ +// # Content Test +// Test the content screen, uses the editor to create dummy content + +/*globals casper, __utils__, url, testPost, newUser */ + +CasperTest.emberBegin("Content screen is correct", 17, function suite(test) { + // First, create a sample post for testing (this should probably be a routine) + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + casper.then(function testViews() { + test.assertExists(".content-view-container", "Content main view is present"); + test.assertExists(".content-list-content", "Content list view is present"); + test.assertExists('.content-list .floatingheader a.button.button-add', 'add new post button exists'); + test.assertEquals(this.getElementAttribute('.content-list .floatingheader a.button.button-add', 'href'), '/ghost/ember/editor/', 'add new post href is correct'); + test.assertExists(".content-list-content li .entry-title", "Content list view has at least one item"); + test.assertSelectorHasText(".content-list-content li:first-of-type h3", testPost.title, "title is present and has content"); + test.assertSelectorHasText(".content-list-content li:first-of-type .entry-meta .status .draft", 'Draft', "status is present has content"); + test.assertExists(".content-preview", "Content preview is present"); + test.assertSelectorHasText('.content-preview header .status', 'Written', 'preview header contains "Written" when post is a draft'); + test.assertSelectorHasText('.content-preview header .author', newUser.name, 'preview header contains author name'); + }); + + casper.then(function testEditPostButton() { + test.assertExists('.content-preview a.post-edit', 'edit post button exists'); + }); + + casper.then(function testPostSettingsMenu() { + test.assertExists('.content-preview a.post-settings', 'post settings button exists'); + this.click('.content-preview a.post-settings'); + }); + + casper.waitUntilVisible('.post-settings-menu', function onSuccess() { + test.assert(true, 'post settings menu should be visible after clicking post-settings icon'); + }); + + casper.then(function postSettingsMenuItems() { + test.assertExists('.post-settings-menu #static-page', 'post settings static page exists'); + test.assertExists('.post-settings-menu a.delete', 'post settings delete this post exists'); + }); + + // A bug is causing this to not always be activated. Uncomment when fixed +// casper.then(function testActiveItem() { +// test.assertExists('.content-list-content li:first-of-type .active', 'first item is active'); +// test.assertDoesntExist('.content-list-content li:nth-of-type(2) .active', 'second item is not active'); +// +// // Ember adds script tags into the list so we need to use nth-of-type +// }).thenClick(".content-list-content li:nth-of-type(2) a", function then() { +// test.assertDoesntExist('.content-list-content li:first-of-type .active', 'first item is not active'); +// test.assertExists('.content-list-content li:nth-of-type(2) .active', 'second item is active'); +// }); +}); + +CasperTest.emberBegin('Content list shows correct post status', 7, function testStaticPageStatus(test) { + CasperTest.Routines.createTestPost.run(true); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + // Select first non-draft, non-static post. Should be second in the list at this stage of testing. + casper.thenClick('.content-list-content li:nth-of-type(2) a'); + + // Test for status of 'Published' + casper.then(function checkStatus() { + test.assertSelectorHasText('.content-list-content li.active .entry-meta .status time', 'Published', + 'status is present and labeled as published'); + }); + + // Test for 'Published' in header + casper.then(function testHeader() { + test.assertSelectorHasText('.content-preview header .status', 'Published', 'preview header contains "Published" when post is published'); + test.assertSelectorHasText('.content-preview header .author', newUser.name, 'preview header contains author name'); + }); + + // Change post to static page + casper.thenClick('a.post-settings'); + + casper.waitUntilVisible('.post-settings-menu', function onSuccess() { + test.assert(true, 'post settings menu should be visible after clicking post-settings icon'); + }); + + casper.thenClick('.post-settings-menu #static-page'); + + casper.waitForSelector('.content-list-content li .entry-meta .status .page', function waitForSuccess() { + test.assertSelectorHasText('.content-list-content li .entry-meta .status .page', 'Page', 'status is Page'); + }, function onTimeout() { + test.assert(false, 'status did not change'); + }); +}); + +CasperTest.emberBegin('Delete post modal', 7, function testDeleteModal(test) { + // Create a post that can be deleted + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + // Open post settings menu + casper.thenClick('.content-preview a.post-settings'); + casper.waitForOpaque('.content-preview .post-settings-menu.open'); + casper.thenClick('.post-settings-menu a.delete'); + + casper.waitUntilVisible('#modal-container', function onSuccess() { + test.assertSelectorHasText( + '.modal-content .modal-header', + 'Are you sure you want to delete this post?', + 'delete modal has correct text'); + }); + + casper.thenClick('.js-button-reject'); + + casper.waitWhileVisible("#modal-container", function onSuccess() { + test.assert(true, "clicking cancel should close the delete post modal"); + }); + + // Test delete + casper.thenClick('.content-preview a.post-settings'); + casper.thenClick('.post-settings-menu a.delete'); + + casper.waitForSelector('#modal-container .modal-content', function onSuccess() { + test.assertExists('.modal-content .js-button-accept', 'delete button exists'); + + // Delete the post + this.click('.modal-content .js-button-accept'); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification from delete post'); + test.assertSelectorHasText('.notification-success', 'Your post has been deleted.'); + }, function onTimeout() { + test.fail('No success notification from delete post'); + }); + }); +}); + +// Uncomment when test is implemented... much needed! +//CasperTest.emberBegin('Infinite scrolling', 2, function suite(test) { +// // Placeholder for infinite scrolling/pagination tests (will need to setup 16+ posts). +// +// casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); +// }); +//}); + +CasperTest.emberBegin("Posts can be marked as featured", 10, function suite(test) { + // Create a sample post + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + // Mark as featured + casper.waitForSelector('.content-preview .unfeatured', function () { + this.click('.content-preview .unfeatured'); + }, function onTimeOut() { + test.assert(false, 'The first post can\'t be marked as featured'); + }); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + test.assertSelectorHasText('.notification-success', 'Post successfully marked as featured.'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); + }); + + casper.waitForSelector('.content-list-content li.featured:first-of-type', function () { + test.assertExists('.content-preview .featured', 'preview pane gets featured class'); + test.assertExists('.content-list-content li.featured:first-of-type', 'content list got a featured star'); + this.click('.notification-success .close'); + }, function onTimeout() { + test.assert(false, 'No featured star appeared in the left pane'); + }); + + // Mark as not featured + casper.waitWhileSelector('.notification-success', function waitForNoSuccess() { + this.click('.content-preview .featured'); + }, function onTimeout() { + test.assert(false, 'Success notification wont go away:('); + }); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + test.assertSelectorHasText('.notification-success', 'Post successfully marked as not featured.'); + test.assertDoesntExist('.content-preview .featured'); + test.assertDoesntExist('.content-list-content li.featured:first-of-type'); + }, function onTimeout() { + test.assert(false, 'Success notification wont go away:('); + }); +}); + +CasperTest.emberBegin('Post url can be changed', 7, function suite(test) { + // Create a sample post + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + casper.thenClick('a.post-settings'); + + casper.waitUntilVisible('.post-settings-menu', function onSuccess() { + test.assert(true, 'post settings menu should be visible after clicking post-settings icon'); + }); + + // Test change permalink + casper.then(function () { + this.fillSelectors('.post-settings-menu form', { + '#url': 'new-url' + }, false); + + this.click('a.post-settings') + }); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + test.assertSelectorHasText('.notification-success', 'Permalink successfully changed to new-url.'); + casper.click('.notification-success a.close'); + }, function onTimeout() { + test.assert(false, 'No success notification'); + }); + + casper.waitWhileSelector('.notification-success', function () { + test.assert(true, 'notification cleared.'); + test.assertNotVisible('.notification-success', 'success notification should not still exist'); + }); +}); + +CasperTest.emberBegin('Post published date can be changed', 7, function suite(test) { + // Create a sample post + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + casper.thenClick('a.post-settings'); + + casper.waitUntilVisible('.post-settings-menu', function onSuccess() { + test.assert(true, 'post settings menu should be visible after clicking post-settings icon'); + }); + + // Test change published date + casper.then(function () { + this.fillSelectors('.post-settings-menu form', { + '.post-setting-date': '22 May 14 @ 23:39' + }, false); + + this.click('a.post-settings') + }); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + test.assertSelectorHasText('.notification-success', 'Publish date successfully changed to 22 May 14 @ 23:39.'); + casper.click('.notification-success a.close'); + }, function onTimeout() { + test.assert(false, 'No success notification'); + }); + + casper.waitWhileSelector('.notification-success', function () { + test.assert(true, 'notification cleared.'); + test.assertNotVisible('.notification-success', 'success notification should not still exist'); + }); +}); + +CasperTest.emberBegin('Post can be changed to static page', 7, function suite(test) { + // Create a sample post + CasperTest.Routines.createTestPost.run(false); + + // Begin test + casper.thenOpenAndWaitForPageLoad('content', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, "Landed on the correct URL"); + }); + + casper.thenClick('.content-preview a.post-settings'); + + casper.waitForOpaque('.content-preview .post-settings-menu.open', function onSuccess() { + test.assert(true, 'post settings should be visible after clicking post-settings icon'); + }); + + casper.thenClick('.post-settings-menu .post-setting-static-page'); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + casper.click('.notification-success a.close'); + }, function onTimeout() { + test.assert(false, 'No success notification'); + }); + + casper.waitWhileSelector('.notification-success', function () { + test.assert(true, 'notification cleared.'); + test.assertNotVisible('.notification-success', 'success notification should not still exist'); + }); + + casper.thenClick('.post-settings-menu .post-setting-static-page'); + + casper.waitForSelector('.notification-success', function waitForSuccess() { + test.assert(true, 'got success notification'); + casper.click('.notification-success a.close'); + }, function onTimeout() { + test.assert(false, 'No success notification'); + }); +}); \ No newline at end of file diff --git a/core/test/functional/client/settings_test.js b/core/test/functional/client/settings_test.js new file mode 100644 index 0000000000..fb9168c41e --- /dev/null +++ b/core/test/functional/client/settings_test.js @@ -0,0 +1,376 @@ +// # Settings Test +// Test the various tabs on the settings page + +/*globals casper, CasperTest, url */ + +// These classes relate to elements which only appear when a given tab is loaded. +// These are used to check that a switch to a tab is complete, or that we are on the right tab. +var generalTabDetector = '.settings-content form#settings-general', + userTabDetector = '.settings-content form.user-profile'; + +CasperTest.emberBegin('Settings screen is correct', 17, function suite(test) { + casper.thenOpenAndWaitForPageLoad('settings', function testTitleAndUrl() { + test.assertTitle('Ghost Admin', 'Ghost admin has no title'); + test.assertUrlMatch(/ghost\/ember\/settings\/general\/$/, 'Landed on the correct URL'); + }); + + casper.then(function testViews() { + test.assertExists('.wrapper', 'Settings main view is present'); + test.assertExists('.settings-sidebar', 'Settings sidebar view is present'); + test.assertExists('.settings-menu', 'Settings menu is present'); + test.assertExists('.settings-menu .general', 'General tab is present'); + test.assertExists('.settings-menu .users', 'Users tab is present'); + test.assertExists('.settings-menu .apps', 'Apps is present'); + test.assertExists('.wrapper', 'Settings main view is present'); + test.assertExists('.settings-content', 'Settings content view is present'); + test.assertExists('.settings-menu .general.active', 'General tab is marked active'); + test.assertExists(generalTabDetector, 'Form is present'); + test.assertSelectorHasText('.settings-content.active h2.title', 'General', 'Title is general'); + }); + + casper.then(function testSwitchingTabs() { + casper.thenClick('.settings-menu .users a'); + casper.waitForSelector(userTabDetector, function then () { + // assert that the right menu item is active + test.assertExists('.settings-menu .users.active', 'User tab is active'); + test.assertDoesntExist('.settings-menu .general.active', 'General tab is not active'); + + // Check Elements on the page are correct? + + }, casper.failOnTimeout(test, 'waitForSelector `userTabDetector` timed out')); + + casper.thenClick('.settings-menu .general a'); + casper.waitForSelector(generalTabDetector, function then () { + // assert that the right menu item is active + test.assertExists('.settings-menu .general.active', 'General tab is active'); + test.assertDoesntExist('.settings-menu .users.active', 'User tab is not active'); + + // Check Elements on the page are correct? + + }, casper.failOnTimeout(test, 'waitForSelector `generalTabDetector` timed out')); + }); + +// ### Saving settings tests +// Please uncomment and fix these as the functionality is implemented + +//CasperTest.emberBegin('Can save settings', 6, function suite(test) { +// casper.thenOpenAndWaitForPageLoad(url + 'ghost/ember/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/ember\/settings\/user\/$/, 'Landed on the correct URL'); +// }); +// +// function handleUserRequest(requestData) { +// // make sure we only get requests from the user pane +// if (requestData.url.indexOf('settings/') !== -1) { +// test.fail('Saving the user pane triggered another settings pane to save'); +// } +// } +// +// function handleSettingsRequest(requestData) { +// // make sure we only get requests from the user pane +// if (requestData.url.indexOf('users/') !== -1) { +// test.fail('Saving a settings pane triggered the user pane to save'); +// } +// } +// +// casper.then(function listenForRequests() { +// casper.on('resource.requested', handleUserRequest); +// }); +// +// casper.thenClick('#user .button-save'); +// casper.waitFor(function successNotification() { +// return this.evaluate(function () { +// return document.querySelectorAll('.js-bb-notification section').length > 0; +// }); +// }, function doneWaiting() { +// test.pass('Waited for notification'); +// }, casper.failOnTimeout(test, 'Saving the user pane did not result in a notification')); +// +// casper.then(function checkUserWasSaved() { +// casper.removeListener('resource.requested', handleUserRequest); +// }); +// +// casper.waitForSelector('.notification-success', function onSuccess() { +// test.assert(true, 'Got success notification'); +// }, casper.failOnTimeout(test, 'No success notification :(')); +// +// casper.thenClick('#main-menu .settings a').then(function testOpeningSettingsTwice() { +// casper.on('resource.requested', handleSettingsRequest); +// test.assertEval(function testUserIsActive() { +// return document.querySelector('.settings-menu .general').classList.contains('active'); +// }, 'general tab is marked active'); +// +// }); +// +// casper.thenClick('#general .button-save').waitFor(function successNotification() { +// return this.evaluate(function () { +// return document.querySelectorAll('.js-bb-notification section').length > 0; +// }); +// }, function doneWaiting() { +// test.pass('Waited for notification'); +// }, casper.failOnTimeout(test, 'Saving the general pane did not result in a notification')); +// +// casper.then(function checkSettingsWereSaved() { +// casper.removeListener('resource.requested', handleSettingsRequest); +// }); +// +// casper.waitForSelector('.notification-success', function onSuccess() { +// test.assert(true, 'Got success notification'); +// }, casper.failOnTimeout(test, 'No success notification :(')); +// +// CasperTest.beforeDone(function () { +// casper.removeListener('resource.requested', handleUserRequest); +// casper.removeListener('resource.requested', handleSettingsRequest); +// }); +}); +// +//CasperTest.begin('Ensure general blog title field length validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#general', function then() { +// this.fill('form#settings-general', { +// 'general[title]': new Array(152).join('a') +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #general timed out')); +// +// casper.thenClick('#general .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'too long'); +// }, casper.failOnTimeout(test, 'Blog title length error did not appear'), 2000); +//}); +// +//CasperTest.begin('Ensure general blog description field length validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#general', function then() { +// this.fillSelectors('form#settings-general', { +// '#blog-description': new Array(202).join('a') +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #general timed out')); +// +// casper.thenClick('#general .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'too long'); +// }, casper.failOnTimeout(test, 'Blog description length error did not appear')); +//}); +// +//CasperTest.begin('Ensure image upload modals display correctly', 6, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// function assertImageUploaderModalThenClose() { +// test.assertSelectorHasText('.description', 'Add image'); +// this.click('#modal-container .js-button-accept'); +// casper.waitForSelector('.notification-success', function onSuccess() { +// test.assert(true, 'Got success notification'); +// }, casper.failOnTimeout(test, 'No success notification')); +// } +// +// // Test Blog Logo Upload Button +// casper.waitForOpaque('#general', function then() { +// this.click('#general .js-modal-logo'); +// }, casper.failOnTimeout(test, 'waitForOpaque #general timed out')); +// +// casper.waitForSelector('#modal-container .modal-content .js-drop-zone .description', assertImageUploaderModalThenClose, +// casper.failOnTimeout(test, 'No upload logo modal container appeared')); +// +// // Test Blog Cover Upload Button +// casper.waitForOpaque('#general', function then() { +// this.click('#general .js-modal-cover'); +// }, casper.failOnTimeout(test, 'waitForOpaque #general timed out')); +// +// casper.waitForSelector('#modal-container .modal-content .js-drop-zone .description', assertImageUploaderModalThenClose, +// casper.failOnTimeout(test, 'No upload cover modal container appeared')); +//}); +// +//CasperTest.begin('User settings screen validates email', 6, function suite(test) { +// var email, brokenEmail; +// +// casper.thenOpen(url + 'ghost/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/user\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.then(function setEmailToInvalid() { +// email = casper.getElementInfo('#user-email').attributes.value; +// brokenEmail = email.replace('.', '-'); +// +// casper.fillSelectors('.user-profile', { +// '#user-email': brokenEmail +// }, false); +// }); +// +// casper.thenClick('#user .button-save'); +// +// casper.waitForResource('/users/'); +// +// casper.waitForSelector('.notification-error', function onSuccess() { +// test.assert(true, 'Got error notification'); +// test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); +// }, casper.failOnTimeout(test, 'No error notification :(')); +// +// casper.then(function resetEmailToValid() { +// casper.fillSelectors('.user-profile', { +// '#user-email': email +// }, false); +// }); +// +// casper.thenClick('#user .button-save'); +// +// casper.waitForResource(/users/); +// +// casper.waitForSelector('.notification-success', function onSuccess() { +// test.assert(true, 'Got success notification'); +// test.assertSelectorDoesntHaveText('.notification-success', '[object Object]'); +// }, casper.failOnTimeout(test, 'No success notification :(')); +//}); +// +//CasperTest.begin('Ensure postsPerPage number field form validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#general', function then() { +// this.fill('form#settings-general', { +// 'general[postsPerPage]': 'notaninteger' +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #general timed out')); +// +// casper.thenClick('#general .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'use a number'); +// }, casper.failOnTimeout(test, 'postsPerPage error did not appear'), 2000); +//}); +// +//CasperTest.begin('Ensure postsPerPage max of 1000', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#general', function then() { +// this.fill('form#settings-general', { +// 'general[postsPerPage]': '1001' +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #general timed out')); +// +// casper.thenClick('#general .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'use a number less than 1000'); +// }, casper.failOnTimeout(test, 'postsPerPage max error did not appear', 2000)); +//}); +// +//CasperTest.begin('Ensure postsPerPage min of 0', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/general/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#general', function then() { +// this.fill('form#settings-general', { +// 'general[postsPerPage]': '-1' +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #general timed out')); +// +// casper.thenClick('#general .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'use a number greater than 0'); +// }, casper.failOnTimeout(test, 'postsPerPage min error did not appear', 2000)); +//}); +// +//CasperTest.begin('User settings screen shows remaining characters for Bio properly', 4, function suite(test) { +// +// function getRemainingBioCharacterCount() { +// return casper.getHTML('.word-count'); +// } +// +// casper.thenOpen(url + 'ghost/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/user\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.then(function checkCharacterCount() { +// test.assert(getRemainingBioCharacterCount() === '200', 'Bio remaining characters is 200'); +// }); +// +// casper.then(function setBioToValid() { +// casper.fillSelectors('.user-profile', { +// '#user-bio': 'asdf\n' // 5 characters +// }, false); +// }); +// +// casper.then(function checkCharacterCount() { +// test.assert(getRemainingBioCharacterCount() === '195', 'Bio remaining characters is 195'); +// }); +//}); +// +//CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/user\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#user', function then() { +// this.fillSelectors('form.user-profile', { +// '#user-bio': new Array(202).join('a') +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #user timed out')); +// +// casper.thenClick('#user .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'is too long'); +// }, casper.failOnTimeout(test, 'Bio field length error did not appear', 2000)); +//}); +// +//CasperTest.begin('Ensure user url field validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/user\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#user', function then() { +// this.fillSelectors('form.user-profile', { +// '#user-website': 'notaurl' +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #user timed out')); +// +// casper.thenClick('#user .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'use a valid url'); +// }, casper.failOnTimeout(test, 'Url validation error did not appear', 2000)); +//}); +// +//CasperTest.begin('Ensure user location field length validation', 3, function suite(test) { +// casper.thenOpen(url + 'ghost/settings/user/', function testTitleAndUrl() { +// test.assertTitle('Ghost Admin', 'Ghost admin has no title'); +// test.assertUrlMatch(/ghost\/settings\/user\/$/, 'Ghost doesn\'t require login this time'); +// }); +// +// casper.waitForSelector('#user', function then() { +// this.fillSelectors('form.user-profile', { +// '#user-location': new Array(1002).join('a') +// }); +// }, casper.failOnTimeout(test, 'waitForSelector #user timed out')); +// +// casper.thenClick('#user .button-save'); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'is too long'); +// }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000)); +//}); \ No newline at end of file diff --git a/core/test/functional/client/signin_test.js b/core/test/functional/client/signin_test.js new file mode 100644 index 0000000000..504a05cbba --- /dev/null +++ b/core/test/functional/client/signin_test.js @@ -0,0 +1,176 @@ +// # Signin Test +// Test that signin works, including testing our spam prevention mechanisms + +/*globals casper, __utils__, url, newUser, user, falseUser */ + +//CasperTest.emberBegin('Ensure Session is Killed', 1, function suite(test) { +// casper.thenOpenAndWaitForPageLoad('signout', function ensureSignedOut() { +// test.assertUrlMatch(/ghost\/ember\/sign/, 'We got redirected to signin or signup page'); +// }); +//}, true); + +CasperTest.emberBegin('Ensure a User is Registered', 3, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signup', function checkUrl() { + test.assertUrlMatch(/ghost\/ember\/signup\/$/, 'Landed on the correct URL'); + }); + + casper.waitForOpaque(".signup-box", + function then() { + this.fillAndSave("#signup", newUser); + }, + function onTimeout() { + test.fail('Sign up form didn\'t fade in.'); + }); + + casper.captureScreenshot('login_register_test.png'); + + casper.waitForSelectorTextChange('.notification-error', function onSuccess() { + test.assertSelectorHasText('.notification-error', 'already registered'); + // If the previous assert succeeds, then we should skip the next check and just pass. + casper.echoConcise('Already registered!'); + casper.captureScreenshot('already_registered.png'); + }, function onTimeout() { + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, 'If we\'re not already registered, we should be logged in.'); + casper.echoConcise('Successfully registered.'); + }, 2000); + + casper.thenOpenAndWaitForPageLoad('signout', function then() { + test.assertUrlMatch(/ghost\/ember\/signin/, 'We got redirected to signin page.'); + }); +}, true); + +CasperTest.emberBegin("Ghost admin will load login page", 3, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/signin\/$/, 'We should be presented with the signin page.'); + + casper.then(function testLink() { + var link = this.evaluate(function (selector) { + return document.querySelector(selector).getAttribute('href'); + }, '.forgotten-password'); + + casper.echoConcise('LINK' + link); + test.assert(link === '/ghost/ember/forgotten/', 'Has correct forgotten password link'); + }); + }); +}, true); + +// Note, this test applies to a global redirect, which sends us to the standard admin. +// Once Ember becomes the standard admin, this test should still pass. +CasperTest.emberBegin('Redirects login to signin', 2, function suite(test) { + casper.start(url + 'ghost/login/', function testRedirect(response) { + test.assertEqual(response.status, 200, 'Response status should be 200.'); + test.assertUrlMatch(/ghost\/signin\//, 'Should be redirected to /signin/.'); + }); +}, true); + +CasperTest.emberBegin("Can't spam it", 4, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signin', function testTitle() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/signin\/$/, 'Landed on the correct URL'); + }); + + casper.waitForOpaque(".login-box", + function then() { + this.fillAndSave("#login", falseUser); + }, + function onTimeout() { + test.fail('Sign in form didn\'t fade in.'); + }); + + + casper.captureScreenshot('login_spam_test.png'); + + casper.wait(200, function doneWait() { + this.fillAndSave("#login", falseUser); + }); + + casper.captureScreenshot('login_spam_test2.png'); + + casper.waitForText('Slow down, there are way too many login attempts!', function onSuccess() { + test.assert(true, 'Spamming the login did result in an error notification'); + test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'Spamming the login did not result in an error notification'); + }); + + // This test causes the spam notification + // add a wait to ensure future tests don't get tripped up by this. + casper.wait(2000); +}, true); + + +CasperTest.emberBegin("Login limit is in place", 4, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/signin\/$/, 'Landed on the correct URL'); + }); + + casper.waitForOpaque(".login-box", + function then() { + this.fillAndSave("#login", falseUser); + }, + function onTimeout() { + test.fail('Sign in form didn\'t fade in.'); + }); + + casper.wait(2100, function doneWait() { + this.fillAndSave("#login", falseUser); + }); + + casper.waitForText('remaining', function onSuccess() { + test.assert(true, 'The login limit is in place.'); + test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'We did not trip the login limit.'); + }); + // This test used login, add a wait to + // ensure future tests don't get tripped up by this. + casper.wait(2000); +}, true); + +CasperTest.emberBegin("Can login to Ghost", 5, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/signin\/$/, 'Landed on the correct URL'); + }); + + casper.waitForOpaque(".login-box", function then() { + this.fillAndSave("#login", user); + }); + + casper.wait(2000); + + casper.waitForResource(/posts/, function testForDashboard() { + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, 'Landed on the correct URL'); + test.assertExists("#global-header", "Global admin header is present"); + test.assertExists(".manage", "We're now on content"); + }, function onTimeOut() { + test.fail('Failed to signin'); + }); +}, true); + +// Uncomment when signin / email validation has been readded to the frontend +//CasperTest.emberBegin('Ensure email field form validation', 3, function suite(test) { +// casper.thenOpenAndWaitForPageLoad('signin', function testTitleAndUrl() { +// test.assertTitle("Ghost Admin", "Ghost admin has no title"); +// test.assertUrlMatch(/ghost\/ember\/signin\/$/, 'Landed on the correct URL'); +// }); +// +// casper.waitForOpaque(".js-login-box", +// function then() { +// this.fillAndSave("form.login-form", { +// 'email': 'notanemail' +// }); +// }, +// function onTimeout() { +// test.fail('Login form didn\'t fade in.'); +// }); +// +// casper.waitForSelectorTextChange('.notification-error', function onSuccess() { +// test.assertSelectorHasText('.notification-error', 'Invalid Email'); +// }, function onTimeout() { +// test.fail('Email validation error did not appear'); +// }, 2000); +// +//}, true); diff --git a/core/test/functional/client/signout_test.js b/core/test/functional/client/signout_test.js new file mode 100644 index 0000000000..7a5f1b9271 --- /dev/null +++ b/core/test/functional/client/signout_test.js @@ -0,0 +1,39 @@ +// # Signout Test +// Test that signout works correctly + +/*globals casper, __utils__, url, testPost, falseUser, email */ +CasperTest.emberBegin("Ghost signout works correctly", 4, function suite(test) { + CasperTest.Routines.register.run(test); + CasperTest.Routines.logout.run(test); + CasperTest.Routines.login.run(test); + + casper.thenOpenAndWaitForPageLoad('root', function then() { + test.assertTitle("Ghost Admin", "Ghost admin has no title"); + test.assertUrlMatch(/ghost\/ember\/\d+\/$/, 'Landed on the correct URL without signing in'); + }); + + casper.thenClick('#usermenu a').waitFor(function checkOpaque() { + return this.evaluate(function () { + var menu = document.querySelector('#usermenu .overlay.open'); + return window.getComputedStyle(menu).getPropertyValue('display') === "block" + && window.getComputedStyle(menu).getPropertyValue('opacity') === "1"; + }); + }); + + casper.captureScreenshot('user-menu-open.png'); + + casper.waitForSelector('.usermenu-signout a'); + casper.thenClick('.usermenu-signout a'); + + casper.waitForSelector('#login').then(function assertSuccess() { + test.assert(true, 'Got login screen'); + }); + + casper.captureScreenshot('user-menu-logout-clicked.png'); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); + }); +}, true); \ No newline at end of file diff --git a/core/test/functional/client/signup_test.js b/core/test/functional/client/signup_test.js new file mode 100644 index 0000000000..9e3f10868f --- /dev/null +++ b/core/test/functional/client/signup_test.js @@ -0,0 +1,32 @@ +// # Signup Test +// Test that signup works correctly + +CasperTest.emberBegin("Ghost signup fails properly", 5, function suite(test) { + casper.thenOpenAndWaitForPageLoad('signup', function then() { + test.assertUrlMatch(/ghost\/ember\/signup\/$/, 'Landed on the correct URL'); + }); + + casper.then(function signupWithShortPassword() { + casper.fillAndSave("#signup", {email: email, password: 'test'}); + }); + + // should now throw a short password error + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); + test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); + }); + + casper.then(function signupWithLongPassword() { + casper.fillAndSave("#signup", {email: email, password: 'testing1234'}); + }); + + // should now throw a 1 user only error + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); + test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); + }); +}, true); \ No newline at end of file diff --git a/core/test/functional/admin/content_test.js b/core/test/functional/clientold/content_test.js similarity index 100% rename from core/test/functional/admin/content_test.js rename to core/test/functional/clientold/content_test.js diff --git a/core/test/functional/admin/editor_test.js b/core/test/functional/clientold/editor_test.js similarity index 100% rename from core/test/functional/admin/editor_test.js rename to core/test/functional/clientold/editor_test.js diff --git a/core/test/functional/admin/flow_test.js b/core/test/functional/clientold/flow_test.js similarity index 100% rename from core/test/functional/admin/flow_test.js rename to core/test/functional/clientold/flow_test.js diff --git a/core/test/functional/admin/login_test.js b/core/test/functional/clientold/login_test.js similarity index 100% rename from core/test/functional/admin/login_test.js rename to core/test/functional/clientold/login_test.js diff --git a/core/test/functional/admin/logout_test.js b/core/test/functional/clientold/logout_test.js similarity index 100% rename from core/test/functional/admin/logout_test.js rename to core/test/functional/clientold/logout_test.js diff --git a/core/test/functional/admin/settings_test.js b/core/test/functional/clientold/settings_test.js similarity index 100% rename from core/test/functional/admin/settings_test.js rename to core/test/functional/clientold/settings_test.js