From b01ec1a3b9c778099960d4142f1a03143ff05fff Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Sun, 7 Jan 2018 23:22:40 -0500 Subject: [PATCH 01/10] add Cypress array.length test --- cypress.json | 3 +++ cypress/fixtures/example.json | 5 +++++ cypress/integration/spec.js | 37 +++++++++++++++++++++++++++++++++++ cypress/plugins/index.js | 17 ++++++++++++++++ cypress/support/commands.js | 25 +++++++++++++++++++++++ cypress/support/index.js | 20 +++++++++++++++++++ package.json | 8 ++++++-- src/components/AppCode.vue | 2 +- 8 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 cypress.json create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/integration/spec.js create mode 100644 cypress/plugins/index.js create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/index.js diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..59783d9 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "viewportHeight": 800 +} 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..ec01fe7 --- /dev/null +++ b/cypress/integration/spec.js @@ -0,0 +1,37 @@ +describe('array-explorer', () => { + beforeEach(() => { + cy.visit('http://localhost:8080') + }) + it('loads', () => { + cy.contains('h1', 'JavaScript Array Explorer') + }) + context('something else', () => { + it('shows length of an array', () => { + cy.get('#firstmethod').select('something else') + cy.get('#methodoptions').select('find the length of the array') + let output + cy + .get('.exampleoutput2') + .invoke('text') + .then(t => { + output = t + }) + // set up spy on `console.log` before + // we can call `eval(input code)` + cy.spy(console, 'log') + cy.get('.usage1').then(v => { + const input = v.text() + // evaluate the input code - we are already spying on console.log! + eval(input) + // the value comes from DOM - so it needs to be + // converted before we can compare it to the + // compute value + expect(console.log).to.have.been.calledWith(JSON.parse(output)) + cy + .get('.exampleoutput2') + .should('have.css', 'opacity', '1') + .and('have.text', String(output)) + }) + }) + }) +}) 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..1e46102 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", @@ -50,7 +52,8 @@ "webpack": "^3.6.0", "webpack-bundle-analyzer": "^2.9.0", "webpack-dev-server": "^2.9.1", - "webpack-merge": "^4.1.0" + "webpack-merge": "^4.1.0", + "cypress": "1.4.1" }, "engines": { "node": ">= 4.0.0", @@ -62,3 +65,4 @@ "not ie <= 8" ] } + diff --git a/src/components/AppCode.vue b/src/components/AppCode.vue index efae836..fbdf8fa 100644 --- a/src/components/AppCode.vue +++ b/src/components/AppCode.vue @@ -116,7 +116,7 @@ export default { }) setTimeout(() => { this.typeOut() - }, 500) + }, 3000) } } } From bba6ec61c4e881d6bfa0b7b4f241555567a125e8 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Sun, 7 Jan 2018 23:38:36 -0500 Subject: [PATCH 02/10] 3 tests for 'something else' working --- cypress.json | 3 +- cypress/integration/spec.js | 73 ++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/cypress.json b/cypress.json index 59783d9..271a312 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,4 @@ { - "viewportHeight": 800 + "viewportHeight": 800, + "defaultCommandTimeout": 6000 } diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index ec01fe7..c356d7f 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -2,36 +2,59 @@ describe('array-explorer', () => { beforeEach(() => { cy.visit('http://localhost:8080') }) + + // utility functions + const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) + const confirmInputAndOutput = () => { + let output + cy + .get('.exampleoutput2') + .invoke('text') + .then(t => { + output = t + }) + // set up spy on `console.log` before + // we can call `eval(input code)` + cy.spy(console, 'log') + cy.get('.usage1').then(v => { + const input = v.text() + // evaluate the input code - we are already spying on console.log! + eval(input) + // the value comes from DOM - so it needs to be + // converted before we can compare it to the + // compute value + expect(console.log).to.have.been.calledWith(JSON.parse(output)) + cy + .get('.exampleoutput2') + .should('have.css', 'opacity', '1') + .and('contain', String(output)) + }) + } + it('loads', () => { cy.contains('h1', 'JavaScript Array Explorer') }) + context('something else', () => { - it('shows length of an array', () => { + beforeEach(() => { cy.get('#firstmethod').select('something else') - cy.get('#methodoptions').select('find the length of the array') - let output - cy - .get('.exampleoutput2') - .invoke('text') - .then(t => { - output = t - }) - // set up spy on `console.log` before - // we can call `eval(input code)` - cy.spy(console, 'log') - cy.get('.usage1').then(v => { - const input = v.text() - // evaluate the input code - we are already spying on console.log! - eval(input) - // the value comes from DOM - so it needs to be - // converted before we can compare it to the - // compute value - expect(console.log).to.have.been.calledWith(JSON.parse(output)) - cy - .get('.exampleoutput2') - .should('have.css', 'opacity', '1') - .and('have.text', String(output)) - }) + }) + + it('shows length of an array', () => { + selectMethodOptions('find the length of the array') + confirmInputAndOutput() + }) + + it('fills array with given value', () => { + selectMethodOptions( + 'fill all the elements of the array with a static value' + ) + confirmInputAndOutput() + }) + + it('copy a sequence of array elements within the array', () => { + selectMethodOptions('copy a sequence of array elements within the array.') + confirmInputAndOutput() }) }) }) From fabacc94afcc89c0c19b9b7ad16ebe6fbc9d2bae Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 00:04:38 -0500 Subject: [PATCH 03/10] generating tests from object of method names --- cypress/integration/spec.js | 70 ++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index c356d7f..c5b60a0 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -5,6 +5,7 @@ describe('array-explorer', () => { // utility functions const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) + const toDouble = text => text.replace(/'/g, '"') const confirmInputAndOutput = () => { let output cy @@ -23,11 +24,12 @@ describe('array-explorer', () => { // the value comes from DOM - so it needs to be // converted before we can compare it to the // compute value - expect(console.log).to.have.been.calledWith(JSON.parse(output)) + // TODO remove comments from the output + expect(console.log).to.have.been.calledWith(JSON.parse(toDouble(output))) cy .get('.exampleoutput2') .should('have.css', 'opacity', '1') - .and('contain', String(output)) + .and('contain', output) }) } @@ -35,26 +37,52 @@ describe('array-explorer', () => { cy.contains('h1', 'JavaScript Array Explorer') }) - context('something else', () => { - beforeEach(() => { - cy.get('#firstmethod').select('something else') - }) - - it('shows length of an array', () => { - selectMethodOptions('find the length of the array') - confirmInputAndOutput() - }) - - it('fills array with given value', () => { - selectMethodOptions( - 'fill all the elements of the array with a static value' - ) - 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.', + ], + } - it('copy a sequence of array elements within the array', () => { - selectMethodOptions('copy a sequence of array elements within the array.') - confirmInputAndOutput() + Object.keys(methods).forEach(method => { + context(method, () => { + beforeEach(() => { + cy.get('#firstmethod').select(method) + }) + methods[method].forEach(secondary => { + it(secondary, () => { + selectMethodOptions(secondary) + confirmInputAndOutput() + }) + }) }) }) }) From 9b11ad39709ff30c74a57405ee4612721a75fb5b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 21:46:55 -0500 Subject: [PATCH 04/10] handle multiple lines cases --- cypress/integration/spec.js | 58 +++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index c5b60a0..f231015 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -1,3 +1,32 @@ +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', () => { beforeEach(() => { cy.visit('http://localhost:8080') @@ -5,12 +34,13 @@ describe('array-explorer', () => { // utility functions const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) - const toDouble = text => text.replace(/'/g, '"') + const confirmInputAndOutput = () => { let output cy .get('.exampleoutput2') .invoke('text') + .then(removeComments) .then(t => { output = t }) @@ -21,15 +51,19 @@ describe('array-explorer', () => { const input = v.text() // evaluate the input code - we are already spying on console.log! eval(input) - // the value comes from DOM - so it needs to be - // converted before we can compare it to the - // compute value - // TODO remove comments from the output - expect(console.log).to.have.been.calledWith(JSON.parse(toDouble(output))) - cy - .get('.exampleoutput2') - .should('have.css', 'opacity', '1') - .and('contain', output) + const values = parseText(output) + // confirm console.log with expected values happened in order + values.forEach((value, k) => { + expect(console.log.getCall(k)).to.have.been.calledWith(value) + }) + cy.get('.exampleoutput2').should('have.css', 'opacity', '1') + if (values.length === 1) { + cy.get('.exampleoutput2').should('contain', output) + } else { + values.forEach(value => { + cy.get('.exampleoutput2').should('contain', String(value)) + }) + } }) } @@ -52,8 +86,8 @@ describe('array-explorer', () => { ], // 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', + // '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': [ From a2fa283a9258efd9e01b114969ed0538995327c7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 22:13:34 -0500 Subject: [PATCH 05/10] all tests but localized date --- cypress/integration/spec.js | 53 +++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index f231015..6b3a5f6 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -1,3 +1,4 @@ +const trim = text => text.trim() const toDoubleQuots = text => text.replace(/'/g, '"') const isMultiLine = text => text.includes('\n') const isCommentLine = text => text.trim().startsWith('//') @@ -36,34 +37,46 @@ describe('array-explorer', () => { const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) const confirmInputAndOutput = () => { - let output cy .get('.exampleoutput2') + .as('output') .invoke('text') .then(removeComments) - .then(t => { - output = t - }) + .as('outputText') + .then(parseText) + .as('outputValues') + // set up spy on `console.log` before // we can call `eval(input code)` cy.spy(console, 'log') - cy.get('.usage1').then(v => { - const input = v.text() - // evaluate the input code - we are already spying on console.log! - eval(input) - const values = parseText(output) - // confirm console.log with expected values happened in order - values.forEach((value, k) => { + cy + .get('.usage1') + .invoke('text') + .then(sourceCode => { + // show message in the command log + cy.log('evaluating', sourceCode) + // evaluate the input code - we are already spying on console.log! + 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) }) - cy.get('.exampleoutput2').should('have.css', 'opacity', '1') - if (values.length === 1) { - cy.get('.exampleoutput2').should('contain', output) - } else { - values.forEach(value => { - cy.get('.exampleoutput2').should('contain', String(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) }) - } }) } @@ -86,8 +99,8 @@ describe('array-explorer', () => { ], // 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', + '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': [ From 4078600aa3a316423a6bc829ca73997823083487 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 23:14:03 -0500 Subject: [PATCH 06/10] split console log to two for locale date --- cypress.json | 4 +- cypress/integration/spec.js | 82 +++++++++++++++++++++--------------- src/components/AppCode.vue | 2 +- store/en/index.js | 84 +++++++++++++++++++------------------ 4 files changed, 94 insertions(+), 78 deletions(-) diff --git a/cypress.json b/cypress.json index 271a312..ca34006 100644 --- a/cypress.json +++ b/cypress.json @@ -1,4 +1,4 @@ { - "viewportHeight": 800, - "defaultCommandTimeout": 6000 + "viewportHeight": 900, + "defaultCommandTimeout": 8000 } diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index 6b3a5f6..173f94a 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -55,8 +55,19 @@ describe('array-explorer', () => { .then(sourceCode => { // show message in the command log cy.log('evaluating', sourceCode) - // evaluate the input code - we are already spying on console.log! - eval(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() }) // confirm console.log with expected values happened in order @@ -84,39 +95,42 @@ describe('array-explorer', () => { cy.contains('h1', 'JavaScript Array Explorer') }) + // 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.', + // ], + // } 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.', - ], + 'return a string': ['return a localized string representing the array.'], } Object.keys(methods).forEach(method => { diff --git a/src/components/AppCode.vue b/src/components/AppCode.vue index fbdf8fa..efae836 100644 --- a/src/components/AppCode.vue +++ b/src/components/AppCode.vue @@ -116,7 +116,7 @@ export default { }) setTimeout(() => { this.typeOut() - }, 3000) + }, 500) } } } diff --git a/store/en/index.js b/store/en/index.js index d651362..1397c91 100644 --- a/store/en/index.js +++ b/store/en/index.js @@ -8,7 +8,7 @@ export default { desc: 'Adds and/or removes elements from an array.', example: `arr.splice(2, 0, 'tacos');
console.log(arr);`, - output: `[5, 1, 'tacos', 8]` + output: `[5, 1, 'tacos', 8]`, }, { name: 'push', @@ -17,7 +17,7 @@ export default { 'Adds one or more elements to the end of an array and returns the new length of the array.', example: `arr.push(2);
console.log(arr);`, - output: '[5, 1, 8, 2]' + output: '[5, 1, 8, 2]', }, { name: 'unshift', @@ -26,7 +26,7 @@ export default { 'Adds one or more elements to the front of an array and returns the new length of the array.', example: `arr.unshift(2, 7);
console.log(arr);`, - output: '[2, 7, 5, 1, 8]' + output: '[2, 7, 5, 1, 8]', }, { name: 'concat', @@ -36,8 +36,8 @@ export default { example: `let arr2 = ['a', 'b', 'c'];
let arr3 = arr.concat(arr2);
console.log(arr3);`, - output: `[5, 1, 8, 'a', 'b', 'c']` - } + output: `[5, 1, 8, 'a', 'b', 'c']`, + }, ], removing: [ { @@ -46,7 +46,7 @@ export default { desc: 'Adds and/or removes elements from an array.', example: `arr.splice(2, 1);
console.log(arr);`, - output: `[5, 1]` + output: `[5, 1]`, }, { name: 'pop', @@ -55,7 +55,7 @@ export default { 'Removes the last element from an array and returns that element.', example: `arr.pop();
console.log(arr);`, - output: `[5, 1]` + output: `[5, 1]`, }, { name: 'shift', @@ -64,7 +64,7 @@ export default { 'Removes the first element from an array and returns that element.', example: `arr.shift();
console.log(arr);`, - output: `[1, 8]` + output: `[1, 8]`, }, { name: 'slice', @@ -76,8 +76,8 @@ export default { console.log(arr);
console.log(slicedArr);`, output: `[5, 1, 8]
- [1, 8]` - } + [1, 8]`, + }, ], string: [ { @@ -86,14 +86,14 @@ export default { desc: `Joins all elements of an array into a string. You can join it together as is or with something in between, 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()];
const arrString = arr.toLocaleString();
const dateString = date.toLocaleString();
- console.log(arrString, dateString);`, - output: `"5,1,8 12/26/2017, 6:54:49 PM"` - } + console.log(arrString);
+ console.log(dateString);`, + output: `"5,1,8"
+ "12/26/2017, 6:54:49 PM"`, + }, ], ordering: [ { @@ -115,7 +117,7 @@ export default { 'Reverses the order of the elements of an array in place — the first becomes the last, and the last becomes the first.', example: `arr.reverse();
console.log(arr);`, - output: `[8, 1, 5]` + output: `[8, 1, 5]`, }, { name: 'sort', @@ -125,8 +127,8 @@ export default { Important note: If compareFunction is not supplied, elements are sorted by converting them to strings and comparing strings in Unicode code point order. For example, "Banana" comes before "cherry". In a numeric sort, 9 comes before 80, but because numbers are converted to strings, "80" comes before "9" in Unicode order. The docs have more information to clarify.`, example: `arr.sort();
console.log(arr);`, - output: `[1, 5, 8]` - } + output: `[1, 5, 8]`, + }, ], other: [ { @@ -134,7 +136,7 @@ export default { shortDesc: 'find the length of the array', desc: 'Returns the number of elements in that array.', example: `console.log(arr.length);`, - output: `3` + output: `3`, }, { name: 'fill', @@ -143,7 +145,7 @@ export default { 'Fills all the elements of an array from a start index to an end index with a static value.', example: `arr.fill(2);
console.log(arr);`, - output: `[2, 2, 2]` + output: `[2, 2, 2]`, }, { name: 'copyWithin', @@ -152,8 +154,8 @@ export default { 'Copies a sequence of array elements within the array. You can specify either just the ending element (where begin will default to zero) or both the beginning and the end, comma-separated.', example: `arr.copyWithin(1);
console.log(arr);`, - output: `[5, 5, 1]` - } + output: `[5, 5, 1]`, + }, ], iterate: [ { @@ -166,7 +168,7 @@ export default { });`, output: `5
1
- 8` + 8`, }, { name: 'map', @@ -176,7 +178,7 @@ export default { 'Creates a new array with the results of calling a provided function on every element in this array.', example: `let map = arr.map(x => x + 1);
console.log(map);`, - output: `[6, 2, 9]` + output: `[6, 2, 9]`, }, { name: 'entries', @@ -187,8 +189,8 @@ export default { console.log(iterator.next().value);`, output: `[0, 5]
// the 0 is the index,
- // the 5 is the first number` - } + // the 5 is the first number`, + }, ], find: { single: [ @@ -198,7 +200,7 @@ export default { desc: 'Determines whether an array contains a certain element, returning true or false as appropriate.', example: `console.log(arr.includes(1));`, - output: `true` + output: `true`, }, { name: 'indexOf', @@ -206,7 +208,7 @@ export default { desc: 'Returns the first index at which a given element can be found in the array, or -1 if it is not present.', example: `console.log(arr.indexOf(5));`, - output: `0` + output: `0`, }, { name: 'lastIndexOf', @@ -214,7 +216,7 @@ export default { desc: 'Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.', example: `console.log(arr.lastIndexOf(5));`, - output: `0` + output: `0`, }, { name: 'find', @@ -223,7 +225,7 @@ export default { 'Returns the found value in the array, if an element in the array satisfies the provided testing function or undefined if not found. Similar to findIndex(), but it returns the item instead of the index.', example: `let isTiny = (el) => el < 2;
console.log(arr.find(isTiny));`, - output: `1` + output: `1`, }, { name: 'findIndex', @@ -232,7 +234,7 @@ export default { 'Returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned. Similar to find(), but it returns the index instead of the item.', example: `let isBig = (el) => el > 6;
console.log(arr.findIndex(isBig));`, - output: `2` + output: `2`, }, { name: 'reduce', @@ -241,7 +243,7 @@ export default { 'Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.', example: `let reducer = (a, b) => a + b;
console.log(arr.reduce(reducer));`, - output: `14` + output: `14`, }, { name: 'reduceRight', @@ -251,8 +253,8 @@ export default { example: `[arr, [0, 1]].reduceRight((a, b) => {
  return a.concat(b)
}, [])`, - output: `[0, 1, 5, 1, 8]` - } + output: `[0, 1, 5, 1, 8]`, + }, ], many: [ { @@ -262,7 +264,7 @@ export default { 'Creates a new array with all of the elements of this array for which the provided filtering function returns true.', example: `let filtered = arr.filter(el => el > 4);
console.log(filtered)`, - output: `[5, 8]` + output: `[5, 8]`, }, { name: 'every', @@ -271,7 +273,7 @@ export default { 'Returns true if every element in this array satisfies the provided testing function.', example: `let isSmall = (el) => el < 10;
console.log(arr.every(isSmall));`, - output: `true` + output: `true`, }, { name: 'some', @@ -280,9 +282,9 @@ export default { 'Returns true if at least one element in this array satisfies the provided testing function.', example: `let biggerThan4 = (el) => el > 4;
console.log(arr.some(biggerThan4));`, - output: `true` - } - ] - } - } + output: `true`, + }, + ], + }, + }, } From 2176810e1c6bbeae97921b8f3b7f7b4f7f8d34c7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 23:20:31 -0500 Subject: [PATCH 07/10] enable all tests --- cypress/integration/spec.js | 67 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index 173f94a..533ae90 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -95,42 +95,39 @@ describe('array-explorer', () => { cy.contains('h1', 'JavaScript Array Explorer') }) - // 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.', - // ], - // } const methods = { - 'return a string': ['return a localized string representing the array.'], + '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 => { From 9afc1aaf322284a8de6fb74eaa2cf007d8ecaf0a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 23:32:09 -0500 Subject: [PATCH 08/10] add one russian language test --- cypress/integration/spec.js | 10 +++++++++- src/components/LocaleSwitcher.vue | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index 533ae90..bea13ef 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -34,6 +34,7 @@ describe('array-explorer', () => { }) // utility functions + const selectMethod = choice => cy.get('#firstmethod').select(choice) const selectMethodOptions = choice => cy.get('#methodoptions').select(choice) const confirmInputAndOutput = () => { @@ -95,6 +96,13 @@ describe('array-explorer', () => { 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', @@ -133,7 +141,7 @@ describe('array-explorer', () => { Object.keys(methods).forEach(method => { context(method, () => { beforeEach(() => { - cy.get('#firstmethod').select(method) + selectMethod(method) }) methods[method].forEach(secondary => { it(secondary, () => { 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 @@
Object Explorer
- Language: -
From 9a07cc43734c150f170cececb5b3f76ce1af9851 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 8 Jan 2018 23:46:45 -0500 Subject: [PATCH 09/10] ignore screenshot and videos --- .gitignore | 8 ++++++-- cypress.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) 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 index ca34006..3e2c0f9 100644 --- a/cypress.json +++ b/cypress.json @@ -1,4 +1,4 @@ { "viewportHeight": 900, - "defaultCommandTimeout": 8000 + "defaultCommandTimeout": 10000 } From dbdb8fdec2c770040b6c5254ac84ae0d40ef74b2 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 10 Jan 2018 11:06:46 -0500 Subject: [PATCH 10/10] speed up tests by faking window timer so that gsap animations are instant --- cypress/integration/spec.js | 15 ++++++++++++++- package.json | 6 +++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cypress/integration/spec.js b/cypress/integration/spec.js index bea13ef..a9c5bc9 100644 --- a/cypress/integration/spec.js +++ b/cypress/integration/spec.js @@ -1,3 +1,5 @@ +const lolex = require('lolex') + const trim = text => text.trim() const toDoubleQuots = text => text.replace(/'/g, '"') const isMultiLine = text => text.includes('\n') @@ -29,8 +31,15 @@ const parseText = text => .map(convertSingleTextLine) describe('array-explorer', () => { + let fakeClock beforeEach(() => { - cy.visit('http://localhost:8080') + cy.visit('http://localhost:8080', { + onBeforeLoad: win => { + fakeClock = lolex.install({ + target: win, + }) + }, + }) }) // utility functions @@ -46,6 +55,9 @@ describe('array-explorer', () => { .as('outputText') .then(parseText) .as('outputValues') + .then(() => { + fakeClock.tick(10000) + }) // set up spy on `console.log` before // we can call `eval(input code)` @@ -69,6 +81,7 @@ describe('array-explorer', () => { // 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 diff --git a/package.json b/package.json index 1e46102..31ba9d6 100644 --- a/package.json +++ b/package.json @@ -31,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", @@ -52,8 +54,7 @@ "webpack": "^3.6.0", "webpack-bundle-analyzer": "^2.9.0", "webpack-dev-server": "^2.9.1", - "webpack-merge": "^4.1.0", - "cypress": "1.4.1" + "webpack-merge": "^4.1.0" }, "engines": { "node": ">= 4.0.0", @@ -65,4 +66,3 @@ "not ie <= 8" ] } -