diff --git a/.eslintrc b/.eslintrc index b32d48eecb..0e641723d6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,14 @@ { + "plugins": [ + "cypress" + ], "extends": [ - "plugin:cypress-dev/general" + "plugin:cypress-dev/general", + "plugin:cypress/recommended" ], "env": { - "node": true + "es6": true, + "node": true, + "cypress/globals": true } } diff --git a/.eslintrc-textlint b/.eslintrc-textlint index fd2775e9cb..f5a6ca70d7 100644 --- a/.eslintrc-textlint +++ b/.eslintrc-textlint @@ -2,19 +2,21 @@ "parserOptions": { "ecmaVersion": 6 }, - "rules": { - "comma-dangle": "off", - "indent": ["error", 2, { "MemberExpression": "off"}], - "no-console": "off", - "no-debugger": "off", - "no-empty": "off", - "no-multi-spaces": "off", - "no-undef": "off", - "no-unused-vars": "off", - "object-shorthand": "off", - "prefer-template": "off", - "quotes": ["error", "single"], - "semi": ["error", "never"], - "space-before-function-paren": "off" - } + "rules": { + "comma-dangle": "off", + "indent": ["error", 2, { "MemberExpression": "off"}], + "no-console": "off", + "no-debugger": "off", + "no-empty": "off", + "no-multi-spaces": "off", + "no-undef": "off", + "no-unused-vars": "off", + "object-shorthand": "off", + "prefer-template": "off", + "quotes": ["error", "single"], + "semi": ["error", "never"], + "space-before-function-paren": "off", + "cypress/no-assigning-return-values": "off", + "cypress/no-unnecessary-waiting": "off" + } } diff --git a/.gitignore b/.gitignore index 29f2e5d495..ad862e27e8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ public/ .DS_Store .deploy*/ support +!cypress/support .idea/ themes/cypress/source/css/vendor/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a1878cafe..e49f8dc731 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,3 @@ { - "standard.enable": false, "eslint.enable": true } diff --git a/__snapshots__/helpers_spec.coffee.js b/__snapshots__/helpers_spec.coffee.js deleted file mode 100644 index 001417c870..0000000000 --- a/__snapshots__/helpers_spec.coffee.js +++ /dev/null @@ -1,10 +0,0 @@ -exports['lib/helpers cheerio wraps document in html tag 1'] = `

foo

` - -exports['lib/helpers cheerio does not wrap fragment in html tag 1'] = `

foo

` - -exports['lib/helpers addPageAnchors is noop if no headings found 1'] = `

foo

` - -exports['lib/helpers addPageAnchors does not wrap with 1'] = `

foo

` - -exports['lib/helpers addPageAnchors does not wrap fragment in html tag 1'] = `

foo

` - diff --git a/__snapshots__/helpers_spec.js b/__snapshots__/helpers_spec.js new file mode 100644 index 0000000000..d75d5e4b05 --- /dev/null +++ b/__snapshots__/helpers_spec.js @@ -0,0 +1,10 @@ +exports['lib/helpers cheerio wraps document in html tag 1'] = '

foo

' + +exports['lib/helpers cheerio does not wrap fragment in html tag 1'] = '

foo

' + +exports['lib/helpers addPageAnchors is noop if no headings found 1'] = '

foo

' + +exports['lib/helpers addPageAnchors does not wrap with 1'] = '

foo

' + +exports['lib/helpers addPageAnchors does not wrap fragment in html tag 1'] = '

foo

' + diff --git a/__snapshots__/url_generator_spec.coffee.js b/__snapshots__/url_generator_spec.js similarity index 100% rename from __snapshots__/url_generator_spec.coffee.js rename to __snapshots__/url_generator_spec.js diff --git a/cypress/integration/api_spec.coffee b/cypress/integration/api_spec.coffee deleted file mode 100644 index ceeaaf5c37..0000000000 --- a/cypress/integration/api_spec.coffee +++ /dev/null @@ -1,153 +0,0 @@ -YAML = require('yamljs') -_ = require('lodash') -{improveUrl} = require('./repo.coffee') - -API_PATH = "/api/api/table-of-contents" -API_HTML = API_PATH + '.html' - -FIRST_PAGE = "table-of-contents.html" -NEXT_PAGE = "catalog-of-events.html" - -describe "API", -> - context "Catalog of events", -> - PAGE = "/api/events/catalog-of-events.html" - - beforeEach -> - cy.visit(PAGE) - - it "loads catalog of events", -> - cy.get('.article-title') - .contains('Catalog of Events') - - context "Main Menu", -> - it "goes straight to 'API' homepage", -> - cy.visit('/') - - cy.contains('API') - .click() - cy.contains('h1', "Table of Contents") - - cy.url() - .should('match', new RegExp(API_HTML)) - - context "Header", -> - beforeEach -> - cy.visit(API_PATH + ".html") - - it "should have link to edit doc", -> - cy.contains("a", "Improve this doc").as("editLink") - # cy.get("@editLink").should("have.attr", "href") - # .and("include", API_PATH + ".md") - cy.get("@editLink") - .should("have.attr", "href") - .and("include", improveUrl) - - context "Sidebar", -> - beforeEach -> - cy.visit(API_PATH + ".html") - - cy.readFile("source/_data/sidebar.yml").then (yamlString) -> - @sidebar = YAML.parse(yamlString) - @sidebarTitles = _.keys(@sidebar.api) - - @sidebarLinkNames = _.reduce @sidebar.api, (memo, nestedObj, key) -> - memo.concat(_.keys(nestedObj)) - , [] - - @sidebarLinks = _.reduce @sidebar.api, (memo, nestedObj, key) -> - memo.concat(_.values(nestedObj)) - , [] - - cy.readFile("themes/cypress/languages/en.yml").then (yamlString) -> - @english = YAML.parse(yamlString) - - it "displays current page as highlighted", -> - cy.get("#sidebar").find("a.current") - .should("have.attr", "href").and("include", API_HTML) - - it "displays English titles in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-title strong").each (displayedTitle, i) -> - englishTitle = @english.sidebar.api[@sidebarTitles[i]] - expect(displayedTitle.text()).to.eq(englishTitle) - - it "displays English link names in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").first(5).each (displayedLink, i) -> - englishLink = @english.sidebar.api[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - it "displays English links in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").each (displayedLink, i) -> - sidebarLink = @sidebarLinks[i] - expect(displayedLink.attr('href')).to.include(sidebarLink) - - context "mobile sidebar menu", -> - beforeEach -> - cy.viewport('iphone-6') - - it "displays sidebar in mobile menu on click", -> - cy.get("#mobile-nav-toggle").click() - cy.get("#mobile-nav-inner").should("be.visible") - .find(".sidebar-li") - .each (displayedLink, i) -> - englishLink = @english.sidebar.api[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - context "Table of Contents", -> - beforeEach -> - cy.visit(API_PATH + ".html") - - it "displays toc", -> - ## skip running this test if we are in interactive mode - return @skip() if Cypress.config("isInteractive") - - cy.get('.sidebar-link').each (linkElement) -> - cy.log(linkElement[0].innerText) - cy.request(linkElement[0].href).its('body').then (body) -> - $body = Cypress.$(body) - - $h1s = $body.find('.article h1').not('.article-title') - $h2s = $body.find('.article h2') - - $h1links = $body.find('.toc-level-1>.toc-link') - $h2links = $body.find('.toc-level-2>.toc-link') - - if $h1links.length - $h1s.each (i, el) -> - $h1 = Cypress.$(el) - $link = $h1links.eq(i) - - expect($link.text()).to.eq($h1.text()) - expect($link.attr('href')).to.eq('#' + $h1.attr('id')) - - if $h2links.length - $h2s.each (i, el) -> - $h2 = Cypress.$(el) - $link = $h2links.eq(i) - - expect($link.text()).to.eq($h2.text()) - expect($link.attr('href')).to.eq('#' + $h2.attr('id')) - - context "Pagination", -> - beforeEach -> - cy.visit(API_PATH + ".html") - - it "does not display Prev link on first page", -> - cy.get(".article-footer-prev").should("not.exist") - - it "displays Next link", -> - cy.get(".article-footer-next").should("have.attr", "href").and("include", NEXT_PAGE) - - describe "click on Next page", -> - beforeEach -> - cy.get(".article-footer-next").click() - cy.url().should("contain", NEXT_PAGE) - - it "should display Prev link", -> - cy.get(".article-footer-prev").should("be.visible") - - it "clicking on Prev link should go back to original page", -> - cy.get(".article-footer-prev").click() - cy.url().should("contain", FIRST_PAGE) diff --git a/cypress/integration/api_spec.js b/cypress/integration/api_spec.js new file mode 100644 index 0000000000..29f0a8683e --- /dev/null +++ b/cypress/integration/api_spec.js @@ -0,0 +1,213 @@ +import YAML from 'yamljs' +import _ from 'lodash' +import { improveUrl } from '../support/repo' + +const API_PATH = '/api/api/table-of-contents' +const API_HTML = `${API_PATH}.html` + +const FIRST_PAGE = 'table-of-contents.html' +const NEXT_PAGE = 'catalog-of-events.html' +const PAGE = '/api/events/catalog-of-events.html' + +describe('API', () => { + context('Catalog of events', () => { + beforeEach(() => { + cy.visit(PAGE) + }) + + it('loads catalog of events', () => + cy.get('.article-title') + .contains('Catalog of Events') + ) + }) + + context('Main Menu', () => + it('goes straight to "API" homepage', () => { + cy.visit('/') + + cy.contains('API') + .click() + cy.contains('h1', 'Table of Contents') + + cy.url() + .should('match', new RegExp(API_HTML)) + }) + ) + + context('Header', () => { + beforeEach(() => { + cy.visit(`${API_PATH}.html`) + }) + + it('should have link to edit doc', () => { + cy.contains('a', 'Improve this doc').as('editLink') + + // cy.get('@editLink').should('have.attr', 'href') + // .and('include', API_PATH + '.md') + cy.get('@editLink') + .should('have.attr', 'href') + .and('include', improveUrl) + }) + }) + + context('Sidebar', () => { + beforeEach(() => { + cy.visit(`${API_PATH}.html`) + + cy.readFile('source/_data/sidebar.yml') + .then(function (yamlString) { + this.sidebar = YAML.parse(yamlString) + this.sidebarTitles = _.keys(this.sidebar.api) + + this.sidebarLinkNames = _.reduce(this.sidebar.api, (memo, nestedObj) => memo.concat(_.keys(nestedObj)) + , []) + + this.sidebarLinks = _.reduce(this.sidebar.api, (memo, nestedObj) => memo.concat(_.values(nestedObj)) + , []) + }) + + cy.readFile('themes/cypress/languages/en.yml') + .then(function (yamlString) { + this.english = YAML.parse(yamlString) + }) + }) + + it('displays current page as highlighted', () => + cy.get('#sidebar').find('a.current') + .should('have.attr', 'href').and('include', API_HTML) + ) + + it('displays English titles in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-title strong') + .each(function (displayedTitle, i) { + const englishTitle = this.english.sidebar.api[this.sidebarTitles[i]] + + expect(displayedTitle.text()).to.eq(englishTitle) + }) + ) + + it('displays English link names in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link').first(5) + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.api[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + ) + + it('displays English links in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link') + .each(function (displayedLink, i) { + const sidebarLink = this.sidebarLinks[i] + + expect(displayedLink.attr('href')).to.include(sidebarLink) + }) + ) + + context('mobile sidebar menu', () => { + beforeEach(() => { + cy.viewport('iphone-6') + }) + + it('displays sidebar in mobile menu on click', () => { + cy.get('#mobile-nav-toggle').click() + + cy.get('#mobile-nav-inner').should('be.visible') + .find('.sidebar-li') + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.api[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + }) + }) + }) + + context('Table of Contents', () => { + beforeEach(() => { + cy.visit(`${API_PATH}.html`) + }) + + it('displays toc', function () { + //# skip running this test if we are in interactive mode + if (Cypress.config('isInteractive')) { + this.skip() + } + + cy.get('.sidebar-link') + .each((linkElement) => { + cy.log(linkElement[0].innerText) + + cy.request(linkElement[0].href).its('body') + .then((body) => { + const $body = Cypress.$(body) + + const $h1s = $body.find('.article h1').not('.article-title') + const $h2s = $body.find('.article h2') + + const $h1links = $body.find('.toc-level-1>.toc-link') + const $h2links = $body.find('.toc-level-2>.toc-link') + + if ($h1links.length) { + $h1s.each((i, el) => { + const $h1 = Cypress.$(el) + const $link = $h1links.eq(i) + + expect($link.text()).to.eq($h1.text()) + + expect($link.attr('href')).to.eq(`#${$h1.attr('id')}`) + }) + } + + if ($h2links.length) { + $h2s.each((i, el) => { + const $h2 = Cypress.$(el) + const $link = $h2links.eq(i) + + expect($link.text()).to.eq($h2.text()) + + expect($link.attr('href')).to.eq(`#${$h2.attr('id')}`) + }) + } + }) + }) + }) + }) + + context('Pagination', () => { + beforeEach(() => { + cy.visit(`${API_PATH}.html`) + }) + + it('does not display Prev link on first page', () => { + cy.get('.article-footer-prev') + .should('not.exist') + }) + + it('displays Next link', () => { + cy.get('.article-footer-next') + .should('have.attr', 'href') + .and('include', NEXT_PAGE) + }) + + describe('click on Next page', () => { + beforeEach(() => { + cy.get('.article-footer-next').click() + cy.url().should('contain', NEXT_PAGE) + }) + + it('should display Prev link', () => { + cy.get('.article-footer-prev') + .should('be.visible') + }) + + it('click on Prev link goeso back to original page', () => { + cy.get('.article-footer-prev').click() + cy.url().should('contain', FIRST_PAGE) + }) + }) + }) +}) diff --git a/cypress/integration/examples_spec.coffee b/cypress/integration/examples_spec.coffee deleted file mode 100644 index 91afd34f78..0000000000 --- a/cypress/integration/examples_spec.coffee +++ /dev/null @@ -1,80 +0,0 @@ -YAML = require('yamljs') -_ = require('lodash') -{improveUrl} = require('./repo.coffee') - -EXAMPLES_PATH = "/examples/examples/recipes" - -describe "Examples", -> - beforeEach -> - cy.server() - cy.visit(EXAMPLES_PATH + ".html") - - context "Main Menu", -> - it "goes straight to 'Recipes'", -> - cy.visit('/') - - cy.contains('Examples') - .click() - cy.contains('h1', "Recipes") - - cy.url() - .should('include', EXAMPLES_PATH) - - context "Header", -> - it "should have link to edit doc", -> - cy.contains("a", "Improve this doc").as("editLink") - # cy.get("@editLink").should("have.attr", "href") - # .and("include", GUIDES_PATH + ".md") - cy.get("@editLink").should("have.attr", "href") - .and("include", improveUrl) - - context "Sidebar", -> - beforeEach -> - cy.readFile("source/_data/sidebar.yml").then (yamlString) -> - @sidebar = YAML.parse(yamlString) - @sidebarTitles = _.keys(@sidebar.examples) - - @sidebarLinkNames = _.reduce @sidebar.examples, (memo, nestedObj, key) -> - memo.concat(_.keys(nestedObj)) - , [] - - @sidebarLinks = _.reduce @sidebar.examples, (memo, nestedObj, key) -> - memo.concat(_.values(nestedObj)) - , [] - - cy.readFile("themes/cypress/languages/en.yml").then (yamlString) -> - @english = YAML.parse(yamlString) - - it "displays current page as highlighted", -> - cy.get("#sidebar").find("a.current") - .should("have.attr", "href").and("include", EXAMPLES_PATH + ".html") - - it "displays English titles in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-title strong").each (displayedTitle, i) -> - englishTitle = @english.sidebar.examples[@sidebarTitles[i]] - expect(displayedTitle.text()).to.eq(englishTitle) - - it "displays English link names in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").first(5).each (displayedLink, i) -> - englishLink = @english.sidebar.examples[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - it "displays English links in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").each (displayedLink, i) -> - sidebarLink = @sidebarLinks[i] - expect(displayedLink.attr('href')).to.include(sidebarLink) - - context "mobile sidebar menu", -> - beforeEach -> - cy.viewport('iphone-6') - - it "displays sidebar in mobile menu on click", -> - cy.get("#mobile-nav-toggle").click() - cy.get("#mobile-nav-inner").should("be.visible") - .find(".sidebar-li") - .each (displayedLink, i) -> - englishLink = @english.sidebar.examples[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) diff --git a/cypress/integration/examples_spec.js b/cypress/integration/examples_spec.js new file mode 100644 index 0000000000..5727ba5a2e --- /dev/null +++ b/cypress/integration/examples_spec.js @@ -0,0 +1,109 @@ +import YAML from 'yamljs' +import _ from 'lodash' +import { improveUrl } from '../support/repo' + +const EXAMPLES_PATH = '/examples/examples/recipes' + +describe('Examples', () => { + beforeEach(() => { + cy.server() + cy.visit(`${EXAMPLES_PATH}.html`) + }) + + context('Main Menu', () => + it('goes straight to "Recipes"', () => { + cy.visit('/') + + cy.contains('Examples') + .click() + cy.contains('h1', 'Recipes') + + cy.url() + .should('include', EXAMPLES_PATH) + }) + ) + + context('Header', () => + it('should have link to edit doc', () => { + cy.contains('a', 'Improve this doc').as('editLink') + // cy.get('@editLink').should('have.attr', 'href') + // .and('include', GUIDES_PATH + '.md') + cy.get('@editLink').should('have.attr', 'href') + .and('include', improveUrl) + }) + ) + + context('Sidebar', () => { + beforeEach(() => { + cy.readFile('source/_data/sidebar.yml') + .then(function (yamlString) { + this.sidebar = YAML.parse(yamlString) + this.sidebarTitles = _.keys(this.sidebar.examples) + + this.sidebarLinkNames = _.reduce(this.sidebar.examples, (memo, nestedObj) => memo.concat(_.keys(nestedObj)) + , []) + + this.sidebarLinks = _.reduce(this.sidebar.examples, (memo, nestedObj) => memo.concat(_.values(nestedObj)) + , []) + }) + + cy.readFile('themes/cypress/languages/en.yml') + .then(function (yamlString) { + this.english = YAML.parse(yamlString) + }) + }) + + it('displays current page as highlighted', () => + cy.get('#sidebar').find('a.current') + .should('have.attr', 'href') + .and('include', `${EXAMPLES_PATH}.html`) + ) + + it('displays English titles in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-title strong') + .each(function (displayedTitle, i) { + const englishTitle = this.english.sidebar.examples[this.sidebarTitles[i]] + + expect(displayedTitle.text()).to.eq(englishTitle) + }) + ) + + it('displays English link names in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link').first(5) + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.examples[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + ) + + it('displays English links in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link') + .each(function (displayedLink, i) { + const sidebarLink = this.sidebarLinks[i] + + expect(displayedLink.attr('href')).to.include(sidebarLink) + }) + ) + + context('mobile sidebar menu', () => { + beforeEach(() => { + cy.viewport('iphone-6') + }) + + it('displays sidebar in mobile menu on click', () => { + cy.get('#mobile-nav-toggle').click() + cy.get('#mobile-nav-inner').should('be.visible') + .find('.sidebar-li') + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.examples[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + }) + }) + }) +}) diff --git a/cypress/integration/faq_spec.coffee b/cypress/integration/faq_spec.coffee deleted file mode 100644 index 2b618e3ee7..0000000000 --- a/cypress/integration/faq_spec.coffee +++ /dev/null @@ -1,80 +0,0 @@ -YAML = require('yamljs') -_ = require('lodash') - -FAQ_PATH = "/faq/questions/using-cypress-faq" - -describe "FAQ", -> - beforeEach -> - cy.server() - cy.visit(FAQ_PATH + ".html") - - context "Main Menu", -> - it "goes straight to 'Using Cypress'", -> - cy.visit('/') - - cy.contains('FAQ') - .click() - cy.contains('h1', "Using Cypress") - - cy.url() - .should('include', FAQ_PATH) - - context "Sidebar", -> - beforeEach -> - cy.readFile("source/_data/sidebar.yml").then (yamlString) -> - @sidebar = YAML.parse(yamlString) - @sidebarTitles = _.keys(@sidebar.faq) - - @sidebarLinkNames = _.reduce @sidebar.faq, (memo, nestedObj, key) -> - memo.concat(_.keys(nestedObj)) - , [] - - @sidebarLinks = _.reduce @sidebar.faq, (memo, nestedObj, key) -> - memo.concat(_.values(nestedObj)) - , [] - - cy.readFile("themes/cypress/languages/en.yml").then (yamlString) -> - @english = YAML.parse(yamlString) - - it "displays current page as highlighted", -> - cy.get("#sidebar").find("a.current") - .should("have.attr", "href").and("include", FAQ_PATH + ".html") - - it "displays English titles in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-title strong").each (displayedTitle, i) -> - englishTitle = @english.sidebar.faq[@sidebarTitles[i]] - expect(displayedTitle.text()).to.eq(englishTitle) - - it "displays English link names in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").first(5).each (displayedLink, i) -> - englishLink = @english.sidebar.faq[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - it "displays English links in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").each (displayedLink, i) -> - sidebarLink = @sidebarLinks[i] - expect(displayedLink.attr('href')).to.include(sidebarLink) - - context "mobile sidebar menu", -> - beforeEach -> - cy.viewport('iphone-6') - - it "displays sidebar in mobile menu on click", -> - cy.get("#mobile-nav-toggle").click() - cy.get("#mobile-nav-inner").should("be.visible") - .find(".sidebar-li") - .each (displayedLink, i) -> - englishLink = @english.sidebar.faq[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - context "Table of Contents", -> - it "displays toc links", -> - cy.get('.toc-level-2>.toc-link').as('tocLinks') - - cy.get('.faq h2').not('.article-title').each ($h2, i) => - cy.get('@tocLinks').eq(i).then ($link) => - expect($link.text()).to.eq($h2.text()) - expect($link.attr('href')).to.eq('#' + $h2.attr('id')) diff --git a/cypress/integration/faq_spec.js b/cypress/integration/faq_spec.js new file mode 100644 index 0000000000..d4e9f9c576 --- /dev/null +++ b/cypress/integration/faq_spec.js @@ -0,0 +1,110 @@ +import YAML from 'yamljs' +import _ from 'lodash' + +const FAQ_PATH = '/faq/questions/using-cypress-faq' + +describe('FAQ', () => { + beforeEach(() => { + cy.server() + + cy.visit(`${FAQ_PATH}.html`) + }) + + context('Main Menu', () => + it('goes straight to "Using Cypress"', () => { + cy.visit('/') + + cy.contains('FAQ') + .click() + cy.contains('h1', 'Using Cypress') + + cy.url() + .should('include', FAQ_PATH) + }) + ) + + context('Sidebar', () => { + beforeEach(() => { + cy.readFile('source/_data/sidebar.yml') + .then(function (yamlString) { + this.sidebar = YAML.parse(yamlString) + this.sidebarTitles = _.keys(this.sidebar.faq) + + this.sidebarLinkNames = _.reduce(this.sidebar.faq, (memo, nestedObj) => memo.concat(_.keys(nestedObj)) + , []) + + return this.sidebarLinks = _.reduce(this.sidebar.faq, (memo, nestedObj) => memo.concat(_.values(nestedObj)) + , []) + }) + + cy.readFile('themes/cypress/languages/en.yml') + .then(function (yamlString) { + this.english = YAML.parse(yamlString) + }) + }) + + it('displays current page as highlighted', () => + cy.get('#sidebar').find('a.current') + .should('have.attr', 'href').and('include', `${FAQ_PATH}.html`) + ) + + it('displays English titles in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-title strong').each(function (displayedTitle, i) { + const englishTitle = this.english.sidebar.faq[this.sidebarTitles[i]] + + expect(displayedTitle.text()).to.eq(englishTitle) + }) + ) + + it('displays English link names in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link').first(5) + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.faq[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + ) + + it('displays English links in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link') + .each(function (displayedLink, i) { + const sidebarLink = this.sidebarLinks[i] + + expect(displayedLink.attr('href')).to.include(sidebarLink) + }) + ) + + context('mobile sidebar menu', () => { + beforeEach(() => cy.viewport('iphone-6')) + + it('displays sidebar in mobile menu on click', () => { + cy.get('#mobile-nav-toggle').click() + + cy.get('#mobile-nav-inner').should('be.visible') + .find('.sidebar-li') + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.faq[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + }) + }) + }) + + context('Table of Contents', () => + it('displays toc links', () => { + cy.get('.toc-level-2>.toc-link').as('tocLinks') + + cy.get('.faq h2').not('.article-title').each(($h2, i) => { + cy.get('@tocLinks').eq(i).then(($link) => { + expect($link.text()).to.eq($h2.text()) + + expect($link.attr('href')).to.eq(`#${$h2.attr('id')}`) + }) + }) + }) + ) +}) diff --git a/cypress/integration/guides_spec.coffee b/cypress/integration/guides_spec.coffee deleted file mode 100644 index 96bc15cbae..0000000000 --- a/cypress/integration/guides_spec.coffee +++ /dev/null @@ -1,136 +0,0 @@ -YAML = require('yamljs') -_ = require('lodash') -{improveUrl} = require('./repo.coffee') - -GUIDES_PATH = '/guides/overview/why-cypress.html' - -FIRST_PAGE = "why-cypress.html" -NEXT_PAGE = "key-differences.html" - -describe "Guides", -> - context "Main Menu", -> - it "goes straight to 'Why Cypress?'", -> - cy.visit('/') - - cy.contains('Guides') - .click() - cy.contains('h1', "Why Cypress?") - - cy.url() - .should('include', GUIDES_PATH) - - context "Header", -> - beforeEach -> - cy.visit(GUIDES_PATH) - - it "should have link to edit doc", -> - cy.contains("a", "Improve this doc").as("editLink") - # cy.get("@editLink").should("have.attr", "href") - # .and("include", GUIDES_PATH + ".md") - cy.get("@editLink").should("have.attr", "href") - .and("include", improveUrl) - - context "Sidebar", -> - beforeEach -> - cy.visit(GUIDES_PATH) - - cy.readFile("source/_data/sidebar.yml").then (yamlString) -> - @sidebar = YAML.parse(yamlString) - @sidebarTitles = _.keys(@sidebar.guides) - - @sidebarLinkNames = _.reduce @sidebar.guides, (memo, nestedObj, key) -> - memo.concat(_.keys(nestedObj)) - , [] - - @sidebarLinks = _.reduce @sidebar.guides, (memo, nestedObj, key) -> - memo.concat(_.values(nestedObj)) - , [] - - cy.readFile("themes/cypress/languages/en.yml").then (yamlString) -> - @english = YAML.parse(yamlString) - - it "displays current page as highlighted", -> - cy.get("#sidebar").find("a.current") - .should("have.attr", "href").and("include", FIRST_PAGE) - - it "displays English titles in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-title strong").each (displayedTitle, i) -> - englishTitle = @english.sidebar.guides[@sidebarTitles[i]] - expect(displayedTitle.text()).to.eq(englishTitle) - - it "displays English link names in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").first(5).each (displayedLink, i) -> - englishLink = @english.sidebar.guides[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - it "displays English links in sidebar", -> - cy.get("#sidebar") - .find(".sidebar-link").each (displayedLink, i) -> - sidebarLink = @sidebarLinks[i] - expect(displayedLink.attr('href')).to.include(sidebarLink) - - context "mobile sidebar menu", -> - beforeEach -> - cy.viewport('iphone-6') - - it "displays sidebar in mobile menu on click", -> - cy.get("#mobile-nav-toggle").click() - cy.get("#mobile-nav-inner").should("be.visible") - .find(".sidebar-li") - .each (displayedLink, i) -> - englishLink = @english.sidebar.guides[@sidebarLinkNames[i]] - expect(displayedLink.text().trim()).to.eq(englishLink) - - context "Table of Contents", -> - before -> - cy.visit(GUIDES_PATH) - - it "displays toc", -> - cy.get('.sidebar-link').each (linkElement) -> - cy.log(linkElement[0].innerText) - cy.request(linkElement[0].href).its('body').then (body) -> - $body = Cypress.$(body) - - $h1s = $body.find('.article h1').not('.article-title') - $h2s = $body.find('.article h2') - - $h1links = $body.find('.toc-level-1>.toc-link') - $h2links = $body.find('.toc-level-2>.toc-link') - - $h1s.each (i, el) -> - $h1 = Cypress.$(el) - $link = $h1links.eq(i) - - expect($link.text()).to.eq($h1.text()) - expect($link.attr('href')).to.eq('#' + $h1.attr('id')) - - $h2s.each (i, el) -> - $h2 = Cypress.$(el) - $link = $h2links.eq(i) - - expect($link.text()).to.eq($h2.text()) - expect($link.attr('href')).to.eq('#' + $h2.attr('id')) - - context "Pagination", -> - beforeEach -> - cy.visit(GUIDES_PATH) - - it "does not display Prev link on first page", -> - cy.get(".article-footer-prev").should("not.exist") - - it "displays Next link", -> - cy.get(".article-footer-next").should("have.attr", "href").and("include", NEXT_PAGE) - - describe "click on Next page", -> - beforeEach -> - cy.get(".article-footer-next").click() - cy.url().should("contain", NEXT_PAGE) - - it "should display Prev link", -> - cy.get(".article-footer-prev").should("be.visible") - - it "clicking on Prev link should go back to original page", -> - cy.get(".article-footer-prev").click() - cy.url().should("contain", FIRST_PAGE) diff --git a/cypress/integration/guides_spec.js b/cypress/integration/guides_spec.js new file mode 100644 index 0000000000..7528566b14 --- /dev/null +++ b/cypress/integration/guides_spec.js @@ -0,0 +1,179 @@ +import YAML from 'yamljs' +import _ from 'lodash' +import { improveUrl } from '../support/repo' + +const GUIDES_PATH = '/guides/overview/why-cypress.html' + +const FIRST_PAGE = 'why-cypress.html' +const NEXT_PAGE = 'key-differences.html' + +describe('Guides', () => { + context('Main Menu', () => + it('goes straight to "Why Cypress?"', () => { + cy.visit('/') + + cy.contains('Guides') + .click() + cy.contains('h1', 'Why Cypress?') + + cy.url() + .should('include', GUIDES_PATH) + }) + ) + + context('Header', () => { + beforeEach(() => cy.visit(GUIDES_PATH)) + + it('should have link to edit doc', () => { + cy.contains('a', 'Improve this doc').as('editLink') + // cy.get("@editLink").should("have.attr", "href") + // .and("include", GUIDES_PATH + ".md") + cy.get('@editLink').should('have.attr', 'href') + .and('include', improveUrl) + }) + }) + + context('Sidebar', () => { + beforeEach(() => { + cy.visit(GUIDES_PATH) + + cy.readFile('source/_data/sidebar.yml') + .then(function (yamlString) { + this.sidebar = YAML.parse(yamlString) + this.sidebarTitles = _.keys(this.sidebar.guides) + + this.sidebarLinkNames = _.reduce(this.sidebar.guides, (memo, nestedObj) => memo.concat(_.keys(nestedObj)) + , []) + + this.sidebarLinks = _.reduce(this.sidebar.guides, (memo, nestedObj) => memo.concat(_.values(nestedObj)) + , []) + }) + + cy.readFile('themes/cypress/languages/en.yml') + .then(function (yamlString) { + this.english = YAML.parse(yamlString) + }) + }) + + it('displays current page as highlighted', () => + cy.get('#sidebar').find('a.current') + .should('have.attr', 'href').and('include', FIRST_PAGE) + ) + + it('displays English titles in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-title strong') + .each(function (displayedTitle, i) { + const englishTitle = this.english.sidebar.guides[this.sidebarTitles[i]] + + expect(displayedTitle.text()).to.eq(englishTitle) + }) + ) + + it('displays English link names in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link').first(5) + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.guides[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + ) + + it('displays English links in sidebar', () => + cy.get('#sidebar') + .find('.sidebar-link') + .each(function (displayedLink, i) { + const sidebarLink = this.sidebarLinks[i] + + expect(displayedLink.attr('href')).to.include(sidebarLink) + }) + ) + + context('mobile sidebar menu', () => { + beforeEach(() => cy.viewport('iphone-6')) + + it('displays sidebar in mobile menu on click', () => { + cy.get('#mobile-nav-toggle').click() + cy.get('#mobile-nav-inner').should('be.visible') + .find('.sidebar-li') + .each(function (displayedLink, i) { + const englishLink = this.english.sidebar.guides[this.sidebarLinkNames[i]] + + expect(displayedLink.text().trim()).to.eq(englishLink) + }) + }) + }) + }) + + context('Table of Contents', function () { + before(() => { + cy.visit(GUIDES_PATH) + }) + + it('displays toc', () => + cy.get('.sidebar-link') + .each(function (linkElement) { + cy.log(linkElement[0].innerText) + cy.request(linkElement[0].href).its('body') + .then(function (body) { + const $body = Cypress.$(body) + + const $h1s = $body.find('.article h1').not('.article-title') + const $h2s = $body.find('.article h2') + + const $h1links = $body.find('.toc-level-1>.toc-link') + const $h2links = $body.find('.toc-level-2>.toc-link') + + $h1s.each(function (i, el) { + const $h1 = Cypress.$(el) + const $link = $h1links.eq(i) + + expect($link.text()).to.eq($h1.text()) + expect($link.attr('href')).to.eq(`#${$h1.attr('id')}`) + }) + + $h2s.each(function (i, el) { + const $h2 = Cypress.$(el) + const $link = $h2links.eq(i) + + expect($link.text()).to.eq($h2.text()) + expect($link.attr('href')).to.eq(`#${$h2.attr('id')}`) + }) + }) + }) + ) + }) + + context('Pagination', () => { + beforeEach(() => cy.visit(GUIDES_PATH)) + + it('does not display Prev link on first page', () => { + cy.get('.article-footer-prev').should('not.exist') + }) + + it('displays Next link', () => { + cy.get('.article-footer-next') + .should('have.attr', 'href') + .and('include', NEXT_PAGE) + }) + + describe('click on Next page', () => { + beforeEach(() => { + cy.get('.article-footer-next').click() + + cy.url().should('contain', NEXT_PAGE) + }) + + it('should display Prev link', () => { + cy.get('.article-footer-prev').should('be.visible') + }) + + it('clicking on Prev link should go back to original page', () => { + cy.get('.article-footer-prev').click() + + cy.url().should('contain', FIRST_PAGE) + }) + }) + }) +}) diff --git a/cypress/integration/main_spec.coffee b/cypress/integration/main_spec.coffee deleted file mode 100644 index 29522d3c15..0000000000 --- a/cypress/integration/main_spec.coffee +++ /dev/null @@ -1,171 +0,0 @@ -YAML = require('yamljs') -_ = require('lodash') - -GUIDES_PATH = "/guides/overview/why-cypress" -API_PATH = "/api/api/table-of-contents" -EXAMPLES_PATH = "/examples/examples/recipes" -FAQ_PATH = "/faq/questions/using-cypress-faq" - -describe "Main", -> - beforeEach -> - cy.server() - - context "CSS", -> - beforeEach -> - cy.visit("/") - # wait for the page redirect and load - cy.url().should('contain', 'why-cypress') - - # only works in development environment where each CSS - # file is separate - if Cypress.env('NODE_ENV') is 'development' - it "loads roboto", -> - cy.request("/fonts/vendor/roboto-fontface/css/roboto/roboto-fontface.css") - - it "has limited container height", -> - cy.get('#container') - .then (el) -> - elHeight = getComputedStyle(el[0]).height - viewportHeight = Cypress.config('viewportHeight') - expect(elHeight).to.equal(viewportHeight + 'px') - - it "has app CSS style rules", -> - isAppStyle = (ruleList) -> - ruleList.href.includes('/cypress.css') || # local separate CSS files - ruleList.href.includes('/style') # single bundle in production - - cy.document() - .then (doc) -> - appRules = Cypress._.find(doc.styleSheets, isAppStyle) - expect(appRules, 'app rules are loaded').to.not.be.undefined - cy.log('found App style rules') - containerRules = Cypress._.find(appRules.rules, {selectorText: '#container'}) - expect(containerRules, 'has #container CSS').to.not.be.undefined - cy.log('found CSS rules for', containerRules.selectorText) - - context "Pages", -> - describe "404", -> - it "displays", -> - cy.visit("/404.html") - cy.contains("404") - - describe "Root routes to main guides", -> - beforeEach -> - cy.visit("/") - - it "displays", -> - cy.url().should("include", GUIDES_PATH) - - context "Navigation", -> - beforeEach -> - cy.visit("/") - - it "displays links to pages", -> - cy.contains(".main-nav-link", "Guides") - .should("have.attr", "href").and("include", GUIDES_PATH) - - cy.contains(".main-nav-link", "API") - .should("have.attr", "href").and("include", API_PATH) - - cy.contains(".main-nav-link", "Examples") - .should("have.attr", "href").and("include", EXAMPLES_PATH) - - cy.contains(".main-nav-link", "FAQ") - .should("have.attr", "href").and("include", FAQ_PATH) - - it "displays link to github repo", -> - cy.get(".main-nav-link").find(".fa-github").parent() - .should("have.attr", "href") - .and("eq", "https://github.com/cypress-io/cypress") - - it "displays language dropdown", -> - cy.contains("select", "English").find("option").contains("English") - - describe "active nav", -> - it "highlights guides when on a guides page", -> - cy.visit(GUIDES_PATH + ".html") - cy.contains(".main-nav-link", "Guides") - .should("have.class", "active") - - it "highlights api when on a api page", -> - cy.visit(API_PATH + ".html") - cy.contains(".main-nav-link", "API") - .should("have.class", "active") - - it "highlights examples when on a examples page", -> - cy.visit(EXAMPLES_PATH + ".html") - cy.contains(".main-nav-link", "Examples") - .should("have.class", "active") - - it "highlights FAQ when on a FAQ page", -> - cy.visit(FAQ_PATH + ".html") - cy.contains(".main-nav-link", "FAQ") - .should("have.class", "active") - - context "Search", -> - beforeEach -> - cy.visit("/") - cy.route({ - method: "POST", - url: /algolia/ - response: {"results":[{"hits":[{"hierarchy":{"lvl2":null,"lvl3":null,"lvl0":"Known Issues","lvl1":null,"lvl6":null,"lvl4":null,"lvl5":null},"url":"https://docs-staging.cypress.io/guides/appendices/known-issues.html#content-inner","content":"Missing Commands Some commands have not been implemented in Cypress. Some commands will be implemented in the future and some do not make sense to implement in Cypress. Right click Issue #53 Workaround Oftentimes you can use .invoke() or cy.wrap() to trigger the event or execute the action in the DOM. Example of right clicking on an element using jQuery cy . get ( '#nav' ) . first ( ) . invoke ( 'trigger' , 'contextmenu' ) Example of right clicking on an element without jQuery // need to create the event to later dispatch var e = new Event ( 'contextmenu' , { bubbles : true , cancelable : true } ) // set coordinates of click e . clientX = 451 e . clientY = 68 cy . get ( '#nav' ) . first ( ) . then ( function ( $el ) { $el [ 0 ] . dispatchEvent ( e ) } ) Hover Issue #10 Sometimes an element has specific logic on hover. Maybe the element doesn’t even display to be clickable until you hover over a specific element. Workaround Oftentimes you can use .invoke() or cy.wrap() to show the element before you perform the action. Example of showing an element in order to perform action cy . get ( '.content' ) . invoke ( 'show' ) . click ( ) You can also force the action to be performed on the element regardless of whether the element is visible or not. Example of clicking on a hidden element cy . get ( '.content' ) . click ( { force : true } ) Example of checking a hidden element cy . get ( '.checkbox' ) . check ( { force : true } ) Difficult use cases Cypress does not support the following use cases. Iframes Issue #136 You cannot target elements or interact with anything in an iframe - regardless of it being a same domain or cross domain iframe. This is actively being worked on in Cypress and you’ll first see support for same domain iframes, followed by cross domain (they are much harder to do). Workaround Sit tight, comment on the issue so we know you care about this support, and be patient. OAuth This is related to the iframe issue above, but basically oauth usually will not work. This is one of the hardest things for Cypress to be able to handle as there are so many different implementations and mechanisms. Likely we will be able to support server side oauth redirects, but for client side popups you’ll simply use sinon and stub the oauth response directly in your code. This is actually possible to do right now but we don’t have any good docs or tutorials on it. Workaround Come into Gitter and talk to us about what you’re trying to do. We’ll tell you if you’re able to mock this and how to do it. window.fetch routing and stubbing Issue #95 Support for fetch has not been added but it’s possible to handle in the same way as we handle XHRs . This biggest challenge here is that you can use fetch in Service Workers outside of the global context. We’ll likely have to move routing to the server and handle it in the proxy layer but it should be possible. While we currently provide things like the stack trace and initiator line for XHR’s we will not be able to provide that for fetch . Workaround Sit tight, comment on the issue so we know you care about this support, and be patient","anchor":"content-inner","objectID":"15872310","_snippetResult":{"content":{"value":"to implement in Cypress. Right click Issue #53 Workaround Oftentimes","matchLevel":"full"}},"_highlightResult":{"hierarchy":{"lvl0":{"value":"Known Issues","matchLevel":"none","matchedWords":[]}},"content":{"value":"Missing Commands Some commands have not been implemented in Cypress. Some commands will be implemented in the future and some do not make sense to implement in Cypress. Right click Issue #53 Workaround Oftentimes you can use .invoke() or cy.wrap() to trigger the event or execute the action in the DOM. Example of right clicking on an element using jQuery cy . get ( '#nav' ) . first ( ) . invoke ( 'trigger' , 'contextmenu' ) Example of right clicking on an element without jQuery // need to create the event to later dispatch var e = new Event ( 'contextmenu' , { bubbles : true , cancelable : true } ) // set coordinates of click e . clientX = 451 e . clientY = 68 cy . get ( '#nav' ) . first ( ) . then ( function ( $el ) { $el [ 0 ] . dispatchEvent ( e ) } ) Hover Issue #10 Sometimes an element has specific logic on hover. Maybe the element doesn’t even display to be clickable until you hover over a specific element. Workaround Oftentimes you can use .invoke() or cy.wrap() to show the element before you perform the action. Example of showing an element in order to perform action cy . get ( '.content' ) . invoke ( 'show' ) . click ( ) You can also force the action to be performed on the element regardless of whether the element is visible or not. Example of clicking on a hidden element cy . get ( '.content' ) . click ( { force : true } ) Example of checking a hidden element cy . get ( '.checkbox' ) . check ( { force : true } ) Difficult use cases Cypress does not support the following use cases. Iframes Issue #136 You cannot target elements or interact with anything in an iframe - regardless of it being a same domain or cross domain iframe. This is actively being worked on in Cypress and you’ll first see support for same domain iframes, followed by cross domain (they are much harder to do). Workaround Sit tight, comment on the issue so we know you care about this support, and be patient. OAuth This is related to the iframe issue above, but basically oauth usually will not work. This is one of the hardest things for Cypress to be able to handle as there are so many different implementations and mechanisms. Likely we will be able to support server side oauth redirects, but for client side popups you’ll simply use sinon and stub the oauth response directly in your code. This is actually possible to do right now but we don’t have any good docs or tutorials on it. Workaround Come into Gitter and talk to us about what you’re trying to do. We’ll tell you if you’re able to mock this and how to do it. window.fetch routing and stubbing Issue #95 Support for fetch has not been added but it’s possible to handle in the same way as we handle XHRs . This biggest challenge here is that you can use fetch in Service Workers outside of the global context. We’ll likely have to move routing to the server and handle it in the proxy layer but it should be possible. While we currently provide things like the stack trace and initiator line for XHR’s we will not be able to provide that for fetch . Workaround Sit tight, comment on the issue so we know you care about this support, and be patient","matchLevel":"full","fullyHighlighted":false,"matchedWords":["click","issue","53"]},"hierarchy_camel":[{"lvl0":{"value":"Known Issues","matchLevel":"none","matchedWords":[]}}]}}],"nbHits":1,"page":0,"nbPages":1,"hitsPerPage":5,"processingTimeMS":1,"exhaustiveNbHits":true,"query":"\"click Issue #53\" ","params":"query=%22click%20Issue%20%2353%22%20&hitsPerPage=5","index":"cypress"}]} - }).as("postAlgolia") - - it "posts to Algolia api with correct index on search", -> - cy.get("#search-input").type("g") - cy.wait("@postAlgolia").then (xhr) -> - expect(xhr.requestBody.requests[0].indexName).to.eq("cypress") - - it "displays algolia dropdown on search", -> - cy.get(".ds-dropdown-menu").should("not.be.visible") - cy.get("#search-input").type("g") - cy.wait("@postAlgolia") - cy.get(".ds-dropdown-menu").should("be.visible") - - describe "displays in mobile view", -> - beforeEach -> - cy.viewport('iphone-6') - - it "displays dropdown on search", -> - cy.get(".ds-dropdown-menu").should("not.be.visible") - cy.get("#search-input").type("g") - cy.wait("@postAlgolia") - cy.get(".ds-dropdown-menu").should("be.visible") - - describe "Changelog", -> - beforeEach -> - cy.visit("/guides/references/changelog.html") - - # check if rendering messed up and removed the sidebar - it "has navigation sidebar", -> - cy.get("aside#sidebar") - .should("be.visible") - - if Cypress.env('NODE_ENV') is 'development' - it "has a truncated table of contents", -> - cy.get("aside#article-toc") - .should("be.visible") - .get(".toc-item") - .should("have.length", 6) ## including truncation warning - cy.url() - .should("match", /.+#\d+-\d+-\d+/) - else - it "has a populated table of contents", -> - cy.get("aside#article-toc") - .contains("0.19.0", { timeout: 10000 }) - .click() - cy.url() - .should('include', '#0-19-0') - - describe "Intro to Cypress", -> - beforeEach -> - cy.visit("/guides/core-concepts/introduction-to-cypress.html") - - # check if rendering messed up and removed the sidebar - it "has navigation sidebar", -> - cy.get("aside#sidebar") - .should("be.visible") - - it.skip "displays algolia dropdown on search", -> - # where is this function? have we deleted it ... - testSearchDropDown() diff --git a/cypress/integration/main_spec.js b/cypress/integration/main_spec.js new file mode 100644 index 0000000000..7e02051a2b --- /dev/null +++ b/cypress/integration/main_spec.js @@ -0,0 +1,238 @@ +const GUIDES_PATH = '/guides/overview/why-cypress' +const API_PATH = '/api/api/table-of-contents' +const EXAMPLES_PATH = '/examples/examples/recipes' +const FAQ_PATH = '/faq/questions/using-cypress-faq' + +describe('Main', () => { + beforeEach(() => { + cy.server() + }) + + context('CSS', () => { + beforeEach(() => { + cy.visit('/') + + // wait for the page redirect and load + cy.url().should('contain', 'why-cypress') + }) + + // only works in development environment where each CSS + // file is separate + if (Cypress.env('NODE_ENV') === 'development') { + it('loads roboto', () => { + cy.request('/fonts/vendor/roboto-fontface/css/roboto/roboto-fontface.css') + }) + } + + it('has limited container height', () => + cy.get('#container') + .then((el) => { + const elHeight = getComputedStyle(el[0]).height + const viewportHeight = Cypress.config('viewportHeight') + + expect(elHeight).to.equal(`${viewportHeight}px`) + }) + ) + + it('has app CSS style rules', () => { + const isAppStyle = (ruleList) => + ruleList.href.includes('/cypress.css') || // local separate CSS files + ruleList.href.includes('/style') // single bundle in production + + + cy.document() + .then(function (doc) { + const appRules = Cypress._.find(doc.styleSheets, isAppStyle) + + expect(appRules, 'app rules are loaded').to.not.be.undefined + cy.log('found App style rules') + + const containerRules = Cypress._.find(appRules.rules, { selectorText: '#container' }) + + expect(containerRules, 'has #container CSS').to.not.be.undefined + + cy.log('found CSS rules for', containerRules.selectorText) + }) + }) + }) + + context('Pages', () => { + describe('404', () => + it('displays', () => { + cy.visit('/404.html') + + cy.contains('404') + }) + ) + + describe('Root routes to main guides', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('displays', () => { + cy.url().should('include', GUIDES_PATH) + }) + }) + }) + + context('Navigation', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('displays links to pages', () => { + cy.contains('.main-nav-link', 'Guides') + .should('have.attr', 'href').and('include', GUIDES_PATH) + + cy.contains('.main-nav-link', 'API') + .should('have.attr', 'href').and('include', API_PATH) + + cy.contains('.main-nav-link', 'Examples') + .should('have.attr', 'href').and('include', EXAMPLES_PATH) + + cy.contains('.main-nav-link', 'FAQ') + .should('have.attr', 'href').and('include', FAQ_PATH) + }) + + it('displays link to github repo', () => + cy.get('.main-nav-link').find('.fa-github').parent() + .should('have.attr', 'href') + .and('eq', 'https://github.com/cypress-io/cypress') + ) + + it('displays language dropdown', () => { + cy.contains('select', 'English') + .find('option').contains('English') + }) + + describe('active nav', () => { + it('highlights guides when on a guides page', () => { + cy.visit(`${GUIDES_PATH}.html`) + + cy.contains('.main-nav-link', 'Guides') + .should('have.class', 'active') + }) + + it('highlights api when on a api page', () => { + cy.visit(`${API_PATH}.html`) + + cy.contains('.main-nav-link', 'API') + .should('have.class', 'active') + }) + + it('highlights examples when on a examples page', () => { + cy.visit(`${EXAMPLES_PATH}.html`) + + cy.contains('.main-nav-link', 'Examples') + .should('have.class', 'active') + }) + + it('highlights FAQ when on a FAQ page', () => { + cy.visit(`${FAQ_PATH}.html`) + + cy.contains('.main-nav-link', 'FAQ') + .should('have.class', 'active') + }) + }) + }) + + context('Search', () => { + beforeEach(() => { + cy.visit('/') + + cy.route({ + method: 'POST', + url: /algolia/, + response: { + 'results': [ + { 'hits': [ + { + 'hierarchy': { 'lvl2': null, 'lvl3': null, 'lvl0': 'Known Issues', 'lvl1': null, 'lvl6': null, 'lvl4': null, 'lvl5': null }, + 'url': 'https://docs-staging.cypress.io/guides/appendices/known-issues.html#content-inner', 'content': 'Missing Commands Some commands have not been implemented in Cypress. Some commands will be implemented in the future and some do not make sense to implement in Cypress. Right click Issue #53 Workaround Oftentimes you can use .invoke() or cy.wrap() to trigger the event or execute the action in the DOM. Example of right clicking on an element using jQuery cy . get ( '#nav' ) . first ( ) . invoke ( 'trigger' , 'contextmenu' ) Example of right clicking on an element without jQuery // need to create the event to later dispatch var e = new Event ( 'contextmenu' , { bubbles : true , cancelable : true } ) // set coordinates of click e . clientX = 451 e . clientY = 68 cy . get ( '#nav' ) . first ( ) . then ( function ( $el ) { $el [ 0 ] . dispatchEvent ( e ) } ) Hover Issue #10 Sometimes an element has specific logic on hover. Maybe the element doesn’t even display to be clickable until you hover over a specific element. Workaround Oftentimes you can use .invoke() or cy.wrap() to show the element before you perform the action. Example of showing an element in order to perform action cy . get ( '.content' ) . invoke ( 'show' ) . click ( ) You can also force the action to be performed on the element regardless of whether the element is visible or not. Example of clicking on a hidden element cy . get ( '.content' ) . click ( { force : true } ) Example of checking a hidden element cy . get ( '.checkbox' ) . check ( { force : true } ) Difficult use cases Cypress does not support the following use cases. Iframes Issue #136 You cannot target elements or interact with anything in an iframe - regardless of it being a same domain or cross domain iframe. This is actively being worked on in Cypress and you’ll first see support for same domain iframes, followed by cross domain (they are much harder to do). Workaround Sit tight, comment on the issue so we know you care about this support, and be patient. OAuth This is related to the iframe issue above, but basically oauth usually will not work. This is one of the hardest things for Cypress to be able to handle as there are so many different implementations and mechanisms. Likely we will be able to support server side oauth redirects, but for client side popups you’ll simply use sinon and stub the oauth response directly in your code. This is actually possible to do right now but we don’t have any good docs or tutorials on it. Workaround Come into Gitter and talk to us about what you’re trying to do. We’ll tell you if you’re able to mock this and how to do it. window.fetch routing and stubbing Issue #95 Support for fetch has not been added but it’s possible to handle in the same way as we handle XHRs . This biggest challenge here is that you can use fetch in Service Workers outside of the global context. We’ll likely have to move routing to the server and handle it in the proxy layer but it should be possible. While we currently provide things like the stack trace and initiator line for XHR’s we will not be able to provide that for fetch . Workaround Sit tight, comment on the issue so we know you care about this support, and be patient', 'anchor': 'content-inner', 'objectID': '15872310', '_snippetResult': { 'content': { 'value': 'to implement in Cypress. Right click Issue #53 Workaround Oftentimes', 'matchLevel': 'full' } }, '_highlightResult': { 'hierarchy': { 'lvl0': { 'value': 'Known Issues', 'matchLevel': 'none', 'matchedWords': [] } }, 'content': { 'value': 'Missing Commands Some commands have not been implemented in Cypress. Some commands will be implemented in the future and some do not make sense to implement in Cypress. Right click Issue #53 Workaround Oftentimes you can use .invoke() or cy.wrap() to trigger the event or execute the action in the DOM. Example of right clicking on an element using jQuery cy . get ( \'#nav\' ) . first ( ) . invoke ( \'trigger\' , \'contextmenu\' ) Example of right clicking on an element without jQuery // need to create the event to later dispatch var e = new Event ( \'contextmenu\' , { bubbles : true , cancelable : true } ) // set coordinates of click e . clientX = 451 e . clientY = 68 cy . get ( \'#nav\' ) . first ( ) . then ( function ( $el ) { $el [ 0 ] . dispatchEvent ( e ) } ) Hover Issue #10 Sometimes an element has specific logic on hover. Maybe the element doesn’t even display to be clickable until you hover over a specific element. Workaround Oftentimes you can use .invoke() or cy.wrap() to show the element before you perform the action. Example of showing an element in order to perform action cy . get ( \'.content\' ) . invoke ( \'show\' ) . click ( ) You can also force the action to be performed on the element regardless of whether the element is visible or not. Example of clicking on a hidden element cy . get ( \'.content\' ) . click ( { force : true } ) Example of checking a hidden element cy . get ( \'.checkbox\' ) . check ( { force : true } ) Difficult use cases Cypress does not support the following use cases. Iframes Issue #136 You cannot target elements or interact with anything in an iframe - regardless of it being a same domain or cross domain iframe. This is actively being worked on in Cypress and you’ll first see support for same domain iframes, followed by cross domain (they are much harder to do). Workaround Sit tight, comment on the issue so we know you care about this support, and be patient. OAuth This is related to the iframe issue above, but basically oauth usually will not work. This is one of the hardest things for Cypress to be able to handle as there are so many different implementations and mechanisms. Likely we will be able to support server side oauth redirects, but for client side popups you’ll simply use sinon and stub the oauth response directly in your code. This is actually possible to do right now but we don’t have any good docs or tutorials on it. Workaround Come into Gitter and talk to us about what you’re trying to do. We’ll tell you if you’re able to mock this and how to do it. window.fetch routing and stubbing Issue #95 Support for fetch has not been added but it’s possible to handle in the same way as we handle XHRs . This biggest challenge here is that you can use fetch in Service Workers outside of the global context. We’ll likely have to move routing to the server and handle it in the proxy layer but it should be possible. While we currently provide things like the stack trace and initiator line for XHR’s we will not be able to provide that for fetch . Workaround Sit tight, comment on the issue so we know you care about this support, and be patient', 'matchLevel': 'full', 'fullyHighlighted': false, 'matchedWords': ['click', 'issue', '53'] }, 'hierarchy_camel': [{ 'lvl0': { 'value': 'Known Issues', 'matchLevel': 'none', 'matchedWords': [] } }] }, + }, + ], 'nbHits': 1, 'page': 0, 'nbPages': 1, 'hitsPerPage': 5, 'processingTimeMS': 1, 'exhaustiveNbHits': true, 'query': '"click Issue #53" ', 'params': 'query=%22click%20Issue%20%2353%22%20&hitsPerPage=5', 'index': 'cypress' }, + ], + }, + }).as('postAlgolia') + }) + + it('posts to Algolia api with correct index on search', () => { + cy.get('#search-input').type('g') + + cy.wait('@postAlgolia').then((xhr) => { + expect(xhr.requestBody.requests[0].indexName).to.eq('cypress') + }) + }) + + it('displays algolia dropdown on search', () => { + cy.get('.ds-dropdown-menu').should('not.be.visible') + cy.get('#search-input').type('g') + cy.wait('@postAlgolia') + cy.get('.ds-dropdown-menu').should('be.visible') + }) + + describe('displays in mobile view', () => { + beforeEach(() => { + cy.viewport('iphone-6') + }) + + it('displays dropdown on search', () => { + cy.get('.ds-dropdown-menu').should('not.be.visible') + cy.get('#search-input').type('g') + cy.wait('@postAlgolia') + cy.get('.ds-dropdown-menu').should('be.visible') + }) + }) + }) + + describe('Changelog', () => { + beforeEach(() => { + cy.visit('/guides/references/changelog.html') + }) + + // check if rendering messed up and removed the sidebar + it('has navigation sidebar', () => + cy.get('aside#sidebar') + .should('be.visible') + ) + + if (Cypress.env('NODE_ENV') === 'development') { + it('has a truncated table of contents', () => { + cy.get('aside#article-toc') + .should('be.visible') + .get('.toc-item') + .should('have.length', 6) //# including truncation warning + + cy.url() + .should('match', /.+#\d+-\d+-\d+/) + }) + } else { + it('has a populated table of contents', () => { + cy.get('aside#article-toc') + .contains('0.19.0', { timeout: 10000 }) + .click() + + cy.url() + .should('include', '#0-19-0') + }) + } + }) + + describe('Intro to Cypress', () => { + beforeEach(() => { + cy.visit('/guides/core-concepts/introduction-to-cypress.html') + }) + + // check if rendering messed up and removed the sidebar + it('has navigation sidebar', () => + cy.get('aside#sidebar') + .should('be.visible') + ) + + // it.skip('displays algolia dropdown on search', () => + // // where is this function? have we deleted it ... + // // testSearchDropDown() + // ) + }) +}) diff --git a/cypress/integration/repo.coffee b/cypress/integration/repo.coffee deleted file mode 100644 index 6853e5fbe6..0000000000 --- a/cypress/integration/repo.coffee +++ /dev/null @@ -1,3 +0,0 @@ -repo = "https://github.com/cypress-io/cypress-documentation" -improveUrl = "#{repo}/edit/develop" -module.exports = {repo, improveUrl} diff --git a/cypress/support/defaults.js b/cypress/support/defaults.js index 24d28fd2fe..e69de29bb2 100644 --- a/cypress/support/defaults.js +++ b/cypress/support/defaults.js @@ -1,17 +0,0 @@ -// *********************************************** -// This example defaults.js shows you how to -// customize the internal behavior of Cypress. -// -// The defaults.js file is a great place to -// override defaults used throughout all tests. -// -// *********************************************** -// -// Cypress.Server.defaults({ -// delay: 500, -// whitelist: function(xhr){} -// }) - -// Cypress.Cookies.defaults({ -// whitelist: ["session_id", "remember_token"] -// }) \ No newline at end of file diff --git a/cypress/support/index.js b/cypress/support/index.js index 30c3e0a180..5539e71694 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -1,23 +1,2 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your other test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/guides/configuration#section-global -// *********************************************************** - -// Import commands.js and defaults.js -// using ES2015 syntax: -import "./commands" -import "./defaults" - -// Alternatively you can use CommonJS syntax: -// require("./commands") -// require("./defaults") +import './commands' +import './defaults' diff --git a/cypress/support/repo.js b/cypress/support/repo.js new file mode 100644 index 0000000000..36721ab103 --- /dev/null +++ b/cypress/support/repo.js @@ -0,0 +1,4 @@ +const repo = 'https://github.com/cypress-io/cypress-documentation' +const improveUrl = `${repo}/edit/develop` + +module.exports = { repo, improveUrl } diff --git a/index.js b/index.js index b8dfca831d..0f06fb7b97 100644 --- a/index.js +++ b/index.js @@ -87,4 +87,5 @@ function initHexo () { } -return initHexo() +initHexo() + diff --git a/package-lock.json b/package-lock.json index 98467f404c..ff3277a4a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -330,7 +330,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -362,7 +362,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -408,7 +408,7 @@ }, "@keyv/redis": { "version": "github:bahmutov/keyv-redis#b64f44cd1d1e87893d989b1469068af8292299d5", - "from": "@keyv/redis@github:bahmutov/keyv-redis#b64f44cd1d1e87893d989b1469068af8292299d5", + "from": "github:bahmutov/keyv-redis#b64f44cd1d1e87893d989b1469068af8292299d5", "dev": true, "requires": { "pify": "3.0.0", @@ -2443,7 +2443,7 @@ }, "callsites": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, @@ -3490,7 +3490,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -3517,7 +3517,7 @@ }, "mkdirp": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", "dev": true, "requires": { @@ -3526,7 +3526,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -4772,13 +4772,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -4824,6 +4824,15 @@ } } }, + "eslint-plugin-cypress": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.1.2.tgz", + "integrity": "sha512-53kluZnH8N1SKg0fLh1csQy87NOVnmsHE2iH17uq3z4PgKAacca0fVsn/qhjTB018t0/wIb9WlY9u78iIHzorQ==", + "dev": true, + "requires": { + "globals": "^11.0.1" + } + }, "eslint-plugin-cypress-dev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-cypress-dev/-/eslint-plugin-cypress-dev-2.0.0.tgz", @@ -6274,22 +6283,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "optional": true, "requires": { "delegates": "^1.0.0", @@ -6298,13 +6310,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6312,32 +6324,35 @@ }, "chownr": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "optional": true, "requires": { "ms": "2.0.0" @@ -6345,22 +6360,26 @@ }, "deep-extend": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -6368,12 +6387,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -6388,7 +6409,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -6401,12 +6423,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.21", - "bundled": true, + "resolved": false, + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -6414,7 +6438,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -6422,7 +6447,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -6431,44 +6457,46 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.2.4", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -6476,7 +6504,8 @@ }, "minizlib": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -6484,20 +6513,22 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "optional": true }, "needle": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "optional": true, "requires": { "debug": "^2.1.2", @@ -6507,7 +6538,8 @@ }, "node-pre-gyp": { "version": "0.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -6524,7 +6556,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -6533,12 +6566,14 @@ }, "npm-bundled": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "optional": true }, "npm-packlist": { "version": "1.1.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -6547,7 +6582,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -6558,35 +6594,39 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -6595,17 +6635,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -6616,14 +6659,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -6637,7 +6682,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "optional": true, "requires": { "glob": "^7.0.5" @@ -6645,38 +6691,43 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6685,7 +6736,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -6693,20 +6745,22 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "optional": true, "requires": { "chownr": "^1.0.1", @@ -6720,12 +6774,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "optional": true, "requires": { "string-width": "^1.0.2" @@ -6733,13 +6789,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.2", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, @@ -11049,7 +11105,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -11080,7 +11136,7 @@ }, "ora": { "version": "0.2.3", - "resolved": "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz", + "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", "dev": true, "requires": { @@ -11102,7 +11158,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -11147,7 +11203,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -11175,7 +11231,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -11210,7 +11266,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -11242,7 +11298,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -11550,7 +11606,7 @@ "dependencies": { "ansi-escapes": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, @@ -12310,13 +12366,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -12937,14 +12993,12 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "optional": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -12962,7 +13016,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -13131,7 +13184,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -13143,7 +13195,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -13203,8 +13254,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-glob": { "version": "4.0.0", @@ -13219,7 +13269,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -13228,7 +13277,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -13238,14 +13286,12 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "optional": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "optional": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" }, "micromatch": { "version": "3.1.10", @@ -15437,7 +15483,7 @@ }, "require-uncached": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { diff --git a/package.json b/package.json index 38ebd22ff4..a956a4054a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "deploy-prebuilt": "node ./cy_scripts/deploy.js", "deploy": "echo Deploying built docs", "deps": "deps-ok && dependency-check . --no-default-entries --entry cy_scripts/deploy.js", - "lint": "echo 'Linting scripts...' && eslint --fix *.js cy_scripts/*.js scripts/*.js && echo 'Lint successful'", + "lint": "echo 'Linting scripts...' && eslint --fix *.js cy_scripts/*.js scripts/*.js cypress/integration/*.js && echo 'Lint successful'", "md-lint": "textlint source/*", "postbuild": "gulp post:build && git-last -m -f public/build.json", "postdeploy": "npm run deploy-prebuilt", @@ -75,7 +75,6 @@ "chalk": "2.4.1", "cheerio": "1.0.0-rc.2", "coffee-react": "5.0.1", - "coffee-script": "1.12.7", "colors": "1.3.3", "common-tags": "1.8.0", "concurrent-transform": "1.0.0", @@ -83,6 +82,7 @@ "dependency-check": "3.2.1", "deps-ok": "1.4.1", "eslint": "5.10.0", + "eslint-plugin-cypress": "2.1.2", "eslint-plugin-cypress-dev": "2.0.0", "fluent-ffmpeg": "2.1.2", "fs-extra": "7.0.1", diff --git a/test/spec_helper.coffee b/test/spec_helper.coffee deleted file mode 100644 index 477ab5be17..0000000000 --- a/test/spec_helper.coffee +++ /dev/null @@ -1,22 +0,0 @@ -nock = require("nock") -chai = require("chai") -sinon = require("sinon") -sinonChai = require("sinon-chai") -jest = require('jest') -jestExpect = expect - -global.snapshot = (expected) -> - jestExpect(expected).toMatchSnapshot() - -chai.use(sinonChai) - -global.nock = nock -global.expect = chai.expect -global.sinon = sinon - -beforeEach -> - nock.disableNetConnect() - - -afterEach -> - sinon.restore() diff --git a/test/spec_helper.js b/test/spec_helper.js new file mode 100644 index 0000000000..dafa9b15e5 --- /dev/null +++ b/test/spec_helper.js @@ -0,0 +1,17 @@ +const nock = require('nock') +const chai = require('chai') +const sinon = require('sinon') +const sinonChai = require('sinon-chai') +const jestExpect = expect + +global.snapshot = (expected) => jestExpect(expected).toMatchSnapshot() + +chai.use(sinonChai) + +global.nock = nock +global.expect = chai.expect +global.sinon = sinon + +beforeEach(() => nock.disableNetConnect()) + +afterEach(() => sinon.restore()) diff --git a/test/unit/__snapshots__/helpers.spec.coffee.snap b/test/unit/__snapshots__/helpers.spec.js.snap similarity index 100% rename from test/unit/__snapshots__/helpers.spec.coffee.snap rename to test/unit/__snapshots__/helpers.spec.js.snap diff --git a/test/unit/__snapshots__/url_generator_spec.coffee.snap b/test/unit/__snapshots__/url_generator_spec.js.snap similarity index 100% rename from test/unit/__snapshots__/url_generator_spec.coffee.snap rename to test/unit/__snapshots__/url_generator_spec.js.snap diff --git a/test/unit/helpers.spec.coffee b/test/unit/helpers.spec.coffee deleted file mode 100644 index ade55fab6e..0000000000 --- a/test/unit/helpers.spec.coffee +++ /dev/null @@ -1,32 +0,0 @@ -require("../spec_helper") -# snapshot = require("snap-shot-it") -helpers = require("../../lib/helpers") -cheerio = require("cheerio") - -describe "lib/helpers", -> - process = (str, isDocument) -> - $ = cheerio.load(str, { - useHtmlParser2: true - decodeEntities: false - }, isDocument) - $.html() - - describe "cheerio", -> - it "wraps document in html tag", -> - snapshot(process("

foo

", true)) - - it "does not wrap fragment in html tag", -> - snapshot(process("

foo

", false)) - - describe "addPageAnchors", -> - it "does not wrap fragment in html tag", -> - snapshot(process("

foo

", false)) - - describe "addPageAnchors", -> - it "is noop if no headings found", -> - str = "

foo

" - snapshot(helpers.addPageAnchors(str)) - - it "does not wrap with ", -> - str = '

foo

' - snapshot(helpers.addPageAnchors(str)) diff --git a/test/unit/helpers.spec.js b/test/unit/helpers.spec.js new file mode 100644 index 0000000000..10b586136e --- /dev/null +++ b/test/unit/helpers.spec.js @@ -0,0 +1,45 @@ +require('../spec_helper') + +const helpers = require('../../lib/helpers') +const cheerio = require('cheerio') + +describe('lib/helpers', () => { + const process = (str, isDocument) => { + const $ = cheerio.load(str, { + useHtmlParser2: true, + decodeEntities: false, + }, isDocument) + + return $.html() + } + + describe('cheerio', () => { + it('wraps document in html tag', () => { + return snapshot(process('

foo

', true)) + }) + + it('does not wrap fragment in html tag', () => { + return snapshot(process('

foo

', false)) + }) + }) + + describe('addPageAnchors', () => + it('does not wrap fragment in html tag', () => { + return snapshot(process('

foo

', false)) + }) + ) + + return describe('addPageAnchors', () => { + it('is noop if no headings found', () => { + const str = '

foo

' + + return snapshot(helpers.addPageAnchors(str)) + }) + + return it('does not wrap with ', () => { + const str = '

foo

' + + return snapshot(helpers.addPageAnchors(str)) + }) + }) +}) diff --git a/test/unit/should-deploy.spec.js b/test/unit/should-deploy.spec.js index fcbecd3bbd..b8e648498d 100644 --- a/test/unit/should-deploy.spec.js +++ b/test/unit/should-deploy.spec.js @@ -8,6 +8,7 @@ describe('should-deploy', () => { describe('isRightBranch', () => { it('allows master branch to deploy to production', function () { sinon.stub(git, 'branchName').resolves('master') + return api.isRightBranch('production').then((allowed) => { expect(allowed).to.equal(true) }) @@ -15,6 +16,7 @@ describe('should-deploy', () => { it('develop branch is NOT allowed to deploy to production', function () { sinon.stub(git, 'branchName').resolves('develop') + return api.isRightBranch('production').then((allowed) => { expect(allowed).to.equal(false) }) @@ -22,6 +24,7 @@ describe('should-deploy', () => { it('develop branch is allowed to deploy to staging', function () { sinon.stub(git, 'branchName').resolves('develop') + return api.isRightBranch('staging').then((allowed) => { expect(allowed).to.equal(true) }) @@ -29,6 +32,7 @@ describe('should-deploy', () => { it('random branch is allowed to deploy to staging', function () { sinon.stub(git, 'branchName').resolves('hot-fix-1') + return api.isRightBranch('staging').then((allowed) => { expect(allowed).to.equal(true) }) diff --git a/test/unit/url_generator_spec.coffee b/test/unit/url_generator_spec.coffee deleted file mode 100644 index cec55df396..0000000000 --- a/test/unit/url_generator_spec.coffee +++ /dev/null @@ -1,223 +0,0 @@ -require("../spec_helper") - -fs = require("hexo-fs") -path = require("path") -Promise = require("bluebird") -urlGenerator = require("../../lib/url_generator") - -data = { - guides: { - 'getting-started': { - 'why-cypress': 'why-cypress.html', - 'next-steps': 'next-steps.html' - }, - 'cypress-basics': { - 'overview': 'overview.html', - 'core-concepts': 'core-concepts.html', - 'known-issues': 'known-issues.html' - }, - } - api: { - welcome: { - api: 'api.html' - }, - commands: { - and: 'and.html', - as: 'as.html', - }, - } -} - -describe "lib/url_generator", -> - describe ".normalizeNestedPaths", -> - it "flattens object and returns each keypath to the value", -> - expect(urlGenerator.normalizeNestedPaths(data).flattened).to.deep.eq({ - "why-cypress": "guides/getting-started/why-cypress.html" - "next-steps": "guides/getting-started/next-steps.html" - "overview": "guides/cypress-basics/overview.html" - "core-concepts": "guides/cypress-basics/core-concepts.html" - "known-issues": "guides/cypress-basics/known-issues.html" - "api": "api/welcome/api.html" - "and": "api/commands/and.html" - "as": "api/commands/as.html" - }) - return undefined - - it "expands object keypaths on the values", -> - expect(urlGenerator.normalizeNestedPaths(data).expanded).to.deep.eq({ - guides: { - 'getting-started': { - 'why-cypress': 'guides/getting-started/why-cypress.html', - 'next-steps': 'guides/getting-started/next-steps.html' - }, - 'cypress-basics': { - 'overview': 'guides/cypress-basics/overview.html', - 'core-concepts': 'guides/cypress-basics/core-concepts.html', - 'known-issues': 'guides/cypress-basics/known-issues.html' - }, - } - api: { - welcome: { - api: 'api/welcome/api.html' - }, - commands: { - and: 'api/commands/and.html', - as: 'api/commands/as.html', - }, - } - }) - return undefined - - describe ".findFileBySource", -> - it "finds by key", -> - expect(urlGenerator.findFileBySource(data, "core-concepts")).to.eq( - "guides/cypress-basics/core-concepts.html" - ) - return undefined - - it "finds by property", -> - expect(urlGenerator.findFileBySource(data, "guides/cypress-basics/overview")).to.eq( - "guides/cypress-basics/overview.html" - ) - return undefined - - describe ".getLocalFile", -> - beforeEach -> - sinon.stub(fs, "stat").returns(Promise.resolve()) - - it "requests file", -> - urlGenerator.getLocalFile(data, "as") - .spread (pathToFile, str) -> - expect(pathToFile).to.eq("api/commands/as.html") - expect(str).to.be.a("string") - - it "throws when cannot find file", -> - source = "my-file.md" - href = "foo" - fullUrl = href - urlGenerator.getLocalFile(data, href, source, fullUrl) - .then -> - throw new Error("should have caught error") - .catch (err) -> - snapshot(err.message) - - describe ".validateAndGetUrl", -> - it "fails when given undefined href", -> - render = (str) -> - return Promise.resolve("
notes
") - - urlGenerator.validateAndGetUrl(data, undefined, 'foo', 'content', render ) - .then -> - throw new Error("should have caught error") - .catch (err) -> - [ - "A url tag was not passed an href argument." - "The source file was: foo" - "url tag's text was: content", - ].forEach (msg) -> - expect(err.message).to.include(msg) - - it "fails when external returns non 2xx", -> - nock("https://www.google.com") - .head("/") - .reply(500) - - urlGenerator.validateAndGetUrl(data, "https://www.google.com") - .then -> - throw new Error("should have caught error") - .catch (err) -> - expect(err.message).to.include("Request to: https://www.google.com/ failed. (Status Code 500)") - - it "fails when URL is invalid", -> - urlGenerator.validateAndGetUrl(data, "https://hub.docker.com/[object Object]p>") - .then -> - throw new Error("should have caught error") - .catch (err) -> - expect(err.message).to.include("You must quote the URL: https://hub.docker.com") - - it "verifies local file and caches subsequent requests", -> - markdown = "## Notes\nfoobarbaz" - - render = (str) -> - expect(str).to.eq(markdown) - "
notes
" - - sinon.stub(fs, "readFile").returns(Promise.resolve(markdown)) - - urlGenerator.validateAndGetUrl(data, "and#notes", "", "", render) - .then (pathToFile) -> - expect(pathToFile).to.eq("/api/commands/and.html#notes") - - urlGenerator.validateAndGetUrl(data, "and#notes", "", "", render) - .then (pathToFile) -> - expect(pathToFile).to.eq("/api/commands/and.html#notes") - - expect(fs.readFile).to.be.calledOnce - - it "verifies external url with anchor href matching hash", -> - nock("https://www.google.com") - .get("/") - .reply(200, "assertions") - - urlGenerator.validateAndGetUrl(data, "https://www.google.com/#assertions") - .then (url) -> - expect(url).to.eq("https://www.google.com/#assertions") - - urlGenerator.validateAndGetUrl(data, "https://www.google.com/#assertions") - .then (url) -> - expect(url).to.eq("https://www.google.com/#assertions") - - it "fails when hash is not present in response", -> - nock("https://www.google.com") - .get("/") - .reply(200, "") - - urlGenerator.validateAndGetUrl(data, "https://www.google.com/#foo", "bar.md") - .then -> - throw new Error("should have caught error") - .catch (err) -> - [ - "Constructing {% url %} tag helper failed" - "The source file was: bar.md" - "You referenced a hash that does not exist at: https://www.google.com/", - "Expected to find an element matching the id: #foo or href: #foo" - "The HTML response body was:" - "" - ].forEach (msg) -> - expect(err.message).to.include(msg) - - it "fails when hash is not present in local file", -> - render = (str) -> "" - - sinon.stub(fs, "readFile").returns(Promise.resolve("")) - - urlGenerator.validateAndGetUrl(data, "and#foo", "guides/core-concepts/bar.md", "content", render) - .then -> - throw new Error("should have caught error") - .catch (err) -> - [ - "Constructing {% url %} tag helper failed" - "The source file was: guides/core-concepts/bar.md" - "You referenced a hash that does not exist at: api/commands/and.html", - "Expected to find an element matching the id: #foo or href: #foo" - "The HTML response body was:" - "" - ].forEach (msg) -> - expect(err.message).to.include(msg) - - it "resolves cached values in a promise", -> - urlGenerator.cache.set("foo", "bar") - .then -> - urlGenerator.validateAndGetUrl(data, "foo") - .then (url) -> - expect(url).to.eq("bar") - - it "correctly composes changelog URLs", -> - urlGenerator.validateAndGetUrl(data, "changelog#3-0-0", "", "") - .then (pathToFile) -> - console.log(pathToFile) - expect(pathToFile).to.eq("/guides/references/changelog.html#3-0-0") - - urlGenerator.validateAndGetUrl(data, "changelog", "", "") - .then (pathToFile) -> - expect(pathToFile).to.eq("/guides/references/changelog.html") diff --git a/test/unit/url_generator_spec.js b/test/unit/url_generator_spec.js new file mode 100644 index 0000000000..511b7d2ccd --- /dev/null +++ b/test/unit/url_generator_spec.js @@ -0,0 +1,246 @@ +require('../spec_helper') + +const fs = require('hexo-fs') +const Promise = require('bluebird') +const urlGenerator = require('../../lib/url_generator') + +const data = { + guides: { + 'getting-started': { + 'why-cypress': 'why-cypress.html', + 'next-steps': 'next-steps.html', + }, + 'cypress-basics': { + 'overview': 'overview.html', + 'core-concepts': 'core-concepts.html', + 'known-issues': 'known-issues.html', + }, + }, + api: { + welcome: { + api: 'api.html', + }, + commands: { + and: 'and.html', + as: 'as.html', + }, + }, +} + +describe('lib/url_generator', () => { + describe('.normalizeNestedPaths', () => { + it('flattens object and returns each keypath to the value', () => { + expect(urlGenerator.normalizeNestedPaths(data).flattened).to.deep.eq({ + 'why-cypress': 'guides/getting-started/why-cypress.html', + 'next-steps': 'guides/getting-started/next-steps.html', + 'overview': 'guides/cypress-basics/overview.html', + 'core-concepts': 'guides/cypress-basics/core-concepts.html', + 'known-issues': 'guides/cypress-basics/known-issues.html', + 'api': 'api/welcome/api.html', + 'and': 'api/commands/and.html', + 'as': 'api/commands/as.html', + }) + }) + + it('expands object keypaths on the values', () => { + expect(urlGenerator.normalizeNestedPaths(data).expanded).to.deep.eq({ + guides: { + 'getting-started': { + 'why-cypress': 'guides/getting-started/why-cypress.html', + 'next-steps': 'guides/getting-started/next-steps.html', + }, + 'cypress-basics': { + 'overview': 'guides/cypress-basics/overview.html', + 'core-concepts': 'guides/cypress-basics/core-concepts.html', + 'known-issues': 'guides/cypress-basics/known-issues.html', + }, + }, + api: { + welcome: { + api: 'api/welcome/api.html', + }, + commands: { + and: 'api/commands/and.html', + as: 'api/commands/as.html', + }, + }, + }) + }) + }) + + describe('.findFileBySource', () => { + it('finds by key', () => { + expect(urlGenerator.findFileBySource(data, 'core-concepts')).to.eq( + 'guides/cypress-basics/core-concepts.html' + ) + }) + + it('finds by property', () => { + expect(urlGenerator.findFileBySource(data, 'guides/cypress-basics/overview')).to.eq( + 'guides/cypress-basics/overview.html' + ) + }) + }) + + describe('.getLocalFile', () => { + beforeEach(() => { + global.sinon.stub(fs, 'stat').returns(Promise.resolve()) + }) + + it('requests file', () => + urlGenerator.getLocalFile(data, 'as') + .spread((pathToFile, str) => { + expect(pathToFile).to.eq('api/commands/as.html') + expect(str).to.be.a('string') + }) + ) + + return it('throws when cannot find file', () => { + const source = 'my-file.md' + const href = 'foo' + const fullUrl = href + + return urlGenerator.getLocalFile(data, href, source, fullUrl) + .then(() => { + throw new Error('should have caught error') + }).catch((err) => { + return snapshot(err.message) + }) + }) + }) + + return describe('.validateAndGetUrl', () => { + it('fails when given undefined href', () => { + const render = () => { + return Promise.resolve('
notes
') + } + + return urlGenerator.validateAndGetUrl(data, undefined, 'foo', 'content', render) + .then(() => { + throw new Error('should have caught error') + }).catch((err) => + [ + 'A url tag was not passed an href argument.', + 'The source file was: foo', + 'url tag\'s text was: content', + ].forEach((msg) => { + expect(err.message).to.include(msg) + }) + ) + }) + + it('fails when external returns non 2xx', () => { + global.nock('https://www.google.com') + .head('/') + .reply(500) + + return urlGenerator.validateAndGetUrl(data, 'https://www.google.com') + .then(() => { + throw new Error('should have caught error') + }).catch((err) => expect(err.message).to.include('Request to: https://www.google.com/ failed. (Status Code 500)')) + }) + + it('fails when URL is invalid', () => + urlGenerator.validateAndGetUrl(data, 'https://hub.docker.com/[object Object]p>') + .then(() => { + throw new Error('should have caught error') + }).catch((err) => expect(err.message).to.include('You must quote the URL: https://hub.docker.com')) + ) + + it('verifies local file and caches subsequent requests', () => { + const markdown = '## Notes\nfoobarbaz' + + const render = (str) => { + expect(str).to.eq(markdown) + + return '
notes
' + } + + global.sinon.stub(fs, 'readFile').returns(Promise.resolve(markdown)) + + return urlGenerator.validateAndGetUrl(data, 'and#notes', '', '', render) + .then((pathToFile) => { + expect(pathToFile).to.eq('/api/commands/and.html#notes') + + return urlGenerator.validateAndGetUrl(data, 'and#notes', '', '', render) + }).then((pathToFile) => { + expect(pathToFile).to.eq('/api/commands/and.html#notes') + + return expect(fs.readFile).to.be.calledOnce + }) + }) + + it('verifies external url with anchor href matching hash', () => { + global.nock('https://www.google.com') + .get('/') + .reply(200, 'assertions') + + return urlGenerator.validateAndGetUrl(data, 'https://www.google.com/#assertions') + .then((url) => { + expect(url).to.eq('https://www.google.com/#assertions') + + return urlGenerator.validateAndGetUrl(data, 'https://www.google.com/#assertions') + }).then((url) => expect(url).to.eq('https://www.google.com/#assertions')) + }) + + it('fails when hash is not present in response', () => { + global.nock('https://www.google.com') + .get('/') + .reply(200, '') + + return urlGenerator.validateAndGetUrl(data, 'https://www.google.com/#foo', 'bar.md') + .then(() => { + throw new Error('should have caught error') + }).catch((err) => + [ + 'Constructing {% url %} tag helper failed', + 'The source file was: bar.md', + 'You referenced a hash that does not exist at: https://www.google.com/', + 'Expected to find an element matching the id: #foo or href: #foo', + 'The HTML response body was:', + '', + ].forEach((msg) => expect(err.message).to.include(msg)) + ) + }) + + it('fails when hash is not present in local file', () => { + const render = () => '' + + global.sinon.stub(fs, 'readFile').returns(Promise.resolve('')) + + return urlGenerator.validateAndGetUrl(data, 'and#foo', 'guides/core-concepts/bar.md', 'content', render) + .then(() => { + throw new Error('should have caught error') + }).catch((err) => + [ + 'Constructing {% url %} tag helper failed', + 'The source file was: guides/core-concepts/bar.md', + 'You referenced a hash that does not exist at: api/commands/and.html', + 'Expected to find an element matching the id: #foo or href: #foo', + 'The HTML response body was:', + '', + ].forEach((msg) => expect(err.message).to.include(msg)) + ) + }) + + it('resolves cached values in a promise', () => + urlGenerator.cache.set('foo', 'bar') + .then(() => + urlGenerator.validateAndGetUrl(data, 'foo') + .then((url) => expect(url).to.eq('bar')) + ) + ) + + return it('correctly composes changelog URLs', () => + urlGenerator.validateAndGetUrl(data, 'changelog#3-0-0', '', '') + .then((pathToFile) => { + // eslint-disable-next-line no-console + console.log(pathToFile) + expect(pathToFile).to.eq('/guides/references/changelog.html#3-0-0') + + return urlGenerator.validateAndGetUrl(data, 'changelog', '', '') + .then((pathToFile) => expect(pathToFile).to.eq('/guides/references/changelog.html')) + }) + ) + }) +})