diff --git a/.gitignore b/.gitignore index 3986a6f..54c670c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ yarn-error.log* .AppleDouble .LSOverride -# Icon must end with two +# Icon must end with two Icon # Thumbnails ._* @@ -81,4 +81,8 @@ jspm_packages *.tgz # Yarn Integrity file -.yarn-integrity \ No newline at end of file +.yarn-integrity + +# Cypress test screenshots and videos +cypress/screenshots +cypress/videos diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..3e2c0f9 --- /dev/null +++ b/cypress.json @@ -0,0 +1,4 @@ +{ + "viewportHeight": 900, + "defaultCommandTimeout": 10000 +} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..da18d93 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js new file mode 100644 index 0000000..a9c5bc9 --- /dev/null +++ b/cypress/integration/spec.js @@ -0,0 +1,167 @@ +const lolex = require('lolex') + +const trim = text => text.trim() +const toDoubleQuots = text => text.replace(/'/g, '"') +const isMultiLine = text => text.includes('\n') +const isCommentLine = text => text.trim().startsWith('//') +const isNonCommentLine = text => !isCommentLine(text) +const convertSingleTextLine = text => { + const quoted = toDoubleQuots(text) + try { + return JSON.parse(quoted) + } catch (err) { + console.error('could not parse text') + console.error(quoted) + return + } +} +const removeComments = text => + text + .split('\n') + .filter(isNonCommentLine) + .join('\n') + +// we need to get JavaScript data from the code text +// there might be single quotes, comments - we need to clean this up +// also each output line means there was different console.log call +const parseText = text => + text + .split('\n') + .filter(isNonCommentLine) + .map(convertSingleTextLine) + +describe('array-explorer', () => { + let fakeClock + beforeEach(() => { + cy.visit('http://localhost:8080', { + onBeforeLoad: win => { + fakeClock = lolex.install({ + target: win, + }) + }, + }) + }) + + // utility functions + const selectMethod = choice => cy.get('#firstmethod').select(choice) + const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) + + const confirmInputAndOutput = () => { + cy + .get('.exampleoutput2') + .as('output') + .invoke('text') + .then(removeComments) + .as('outputText') + .then(parseText) + .as('outputValues') + .then(() => { + fakeClock.tick(10000) + }) + + // set up spy on `console.log` before + // we can call `eval(input code)` + cy.spy(console, 'log') + cy + .get('.usage1') + .invoke('text') + .then(sourceCode => { + // show message in the command log + cy.log('evaluating', sourceCode) + // there is one test that has hardcoded output date + // mock clock and pass fake "Date" object into `eval` + // to get the same date when called + var clock = Cypress.sinon.useFakeTimers( + new Date('12/26/2017, 6:54:49 PM').getTime() + ) + { + // evaluate the input code - we are already spying on console.log! + eval('const Date = clock.Date;' + sourceCode) + } + // don't forget to restore system clock + // otherwise good things will not happen + clock.restore() + eval(sourceCode) + }) + + // confirm console.log with expected values happened in order + cy.get('@outputValues').then(outputValues => { + outputValues.forEach((value, k) => { + expect(console.log.getCall(k)).to.have.been.calledWith(value) + }) + }) + + // make sure the output text actually appears + cy.get('@outputText').then(outputText => { + cy.get('@output').should('have.css', 'opacity', '1') + // the only difficulty is with multiline text where there might + // be white space at the start of each line + outputText + .split('\n') + .map(trim) + .forEach(line => { + cy.get('@output').should('contain', line) + }) + }) + } + + it('loads', () => { + cy.contains('h1', 'JavaScript Array Explorer') + }) + + it('works in Russian', () => { + cy.get('[data-attr-cy="language"').select('Russian') + selectMethod('удалить элементы') // remove elements + selectMethodOptions('первый элемент массива') // first element of the array + confirmInputAndOutput() + }) + + const methods = { + 'add items or other arrays': [ + 'element/s to an array', + 'elements to the end of an array', + 'elements to the front of an array', + 'this array to other array(s) and/or value(s)', + ], + 'remove items': [ + 'element/s from an array', + 'the last element of the array', + 'the first element of the array', + 'one or more elements in order for use, leaving the array as is', + ], + // skip "find items" - requires multiple parameters + 'walk over items': [ + 'executing a function I will create for each element', + 'creating a new array from each element with a function I create', + 'creating an iterator object', + ], + 'return a string': [ + 'join all elements of the array into a string', + 'return a string representing the array.', + 'return a localized string representing the array.', + ], + 'order an array': [ + 'reverse the order of the array', + 'sort the items of the array', + ], + 'something else': [ + 'find the length of the array', + 'fill all the elements of the array with a static value', + 'copy a sequence of array elements within the array.', + ], + } + + Object.keys(methods).forEach(method => { + context(method, () => { + beforeEach(() => { + selectMethod(method) + }) + methods[method].forEach(secondary => { + it(secondary, () => { + selectMethodOptions(secondary) + confirmInputAndOutput() + }) + }) + }) + }) +}) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000..fd170fb --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..c1f5a77 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your 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/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/package.json b/package.json index 085fbf1..31ba9d6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", - "build": "node build/build.js" + "build": "node build/build.js", + "test": "cypress run", + "test:gui": "cypress open" }, "dependencies": { "gsap": "^1.20.3", @@ -29,10 +31,12 @@ "chalk": "^2.0.1", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.0", + "cypress": "1.4.1", "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^1.1.4", "friendly-errors-webpack-plugin": "^1.6.1", "html-webpack-plugin": "^2.30.1", + "lolex": "^2.3.1", "node-notifier": "^5.1.2", "optimize-css-assets-webpack-plugin": "^3.2.0", "ora": "^1.2.0", diff --git a/src/components/LocaleSwitcher.vue b/src/components/LocaleSwitcher.vue index 71e5a67..80eae3e 100644 --- a/src/components/LocaleSwitcher.vue +++ b/src/components/LocaleSwitcher.vue @@ -2,8 +2,8 @@
elements.join(' -
')
gives you foo-bar
`,
example: `console.log(arr.join());`,
- output: `"5,1,8"`
+ output: `"5,1,8"`,
},
{
name: 'toString',
shortDesc: 'return a string representing the array.',
desc: 'Returns a string representing the array and its elements.',
example: `console.log(arr.toString());`,
- output: `"5,1,8"`
+ output: `"5,1,8"`,
},
{
name: 'toLocaleString',
@@ -103,9 +103,11 @@ export default {
example: `let date = [new Date()];findIndex()
, but it returns the item instead of the index.',
example: `let isTiny = (el) => el < 2;find()
, but it returns the index instead of the item.',
example: `let isBig = (el) => el > 6;