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 @@
Object Explorer
- Language: -
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`, + }, + ], + }, + }, }