diff --git a/.github/assets/config.png b/.github/assets/config.png index ede80f5..ae39b13 100644 Binary files a/.github/assets/config.png and b/.github/assets/config.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c12d72 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2023-2024 Joshua <data@shard.wtf> + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 6803bde..883bc45 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,46 @@ # @datashard/snapshot -> Adds value / object / DOM element snapshot testing support to Cypress test runner +> Adds support for Value, Object, and Dom Element Snapshot Testing to Cypress -
-Changes between @datashard/snapshot and @cypress/snapshot -
-They're mostly the same, as this is a fork of the Latter, though it's not a drop-in replacement. - -Unlike `@cypress/snapshot`, this saves snapshots in their own files with a sensible default and strives to have ongoing Support for future Cypress Versions - -
- - +## Breaking Changes +> [!WARNING] +The `readFileMaybe` task was required in previous Versions, this has been changed so this Module now uses the `cy.fixture` Command to get the contents of existing files. This means that this module will only be able to write new tests if `updateSnapshots` (previously `SNAPSHOT_UPDATE`) is set to true through Environment Variables or through the Cypress config. +\ +This also means, that previous tests will likely be broken, *please make sure that your tests pass before updating to the latest version of this module* ## Install - -Requires [Node](https://nodejs.org/en/) version 10 or above. +Requires Node 16 or above ```sh -npm install --save-dev @datashard/snapshot +npm i --save-dev @datashard/snapshot ``` ## Import - -After installing, you need to add this snippet within your Cypress Support File (default: `cypress/support/e2e.{js,ts}`) +After Installing, you'll need to add the following import into your Commands/Support File +> by default this will be `cypress/support/e2e.js` ```js -require("@datashard/snapshot").register(); +require('@datashard/snapshot').regsiter() ``` -This registers a new command to create new snapshot or compare value to old snapshot +This will register a new Command `.snapshot()`, to create new Snapshots and once created, to compare their Values. -and add the following to your `cypress.config.{js,ts}` +## Config +You can pass `updateSnapshots` and `useFolders` as options in the `cypress.config.js` file -```js - e2e: { - setupNodeEvents(on, config) { - require("@datashard/snapshot").tasks(on, config) - }, -``` + -> **Note** \ -> \ -> `@datashard/snapshot` **requires** the `readFileMaybe` plugin to be included, which can be easily done using the code above +Alternatively, you can also add `snapshotUpdate` as an Environment Variable to update your snapshots. -# Usage +Simply pass `--env updateSnapshots=true` when running Cypress. -Currently, if you want to take more than one snapshot, you need to pass a Step Name to prevent overwrites / test failures +> If you don't use the default fixture folder, you will also need to add `snapshotPath` to this module's config with the same path you use for `fixtureFolder`. +## Usage + +If properly added, usage of this plugin is rather simple, simply add `.snapshot()` to cypress functions that return valid JSON. + +### Example ```js describe("my tests", () => { it("works", () => { @@ -59,113 +52,27 @@ describe("my tests", () => { }); ``` -In the above case, you can find the stored snapshot in their own files, mentioned above them +Depending on your settings, this module will then save your snapshots as +```ts +// useFolders: false +cypress/fixtures/snapshots/my-tests__works__foo.json +cypress/fixtures/snapshots/my-tests__works__bar.json + +// useFolders: true +cypress/fixtures/snapshots/my-tests/works/foo.json +cypress/fixtures/snapshots/my-tests/works/bar.json -```jsonc -// cypress/fixtures/snapshots/my-tests-works-foo.json -{ "foo": 42 } -// cypress/fixtures/snapshots/my-tests-works-bar.json -{ "bar": 101 } ``` -If you change the site values, the saved snapshot will no longer match, throwing an error +Snapshots will generally be saved using this convention, provided by Cypress: -(picture taken from `cypress/snapshots/Arrays.json`) -![Snapshot mismatch](.github/assets/updated-mismatch.png) - -Click on the `SNAPSHOT` step in the Command Log to see expected and current value printed in the DevTools. - -### Options - -You can control snapshot comparison and behavior through a few options. - -```js -cy.get(...).snapshot({ - snapshotName: 'Snapshot Name', // Overwrite the generated Snapshot name - snapshotPath: 'cypress/fixtures/not_snapshots', // Overwrite where the Snapshot should be stored - json: false // convert DOM elements into JSON -}) // when storing in the snapshot file - -// will save as -// cypress/not_snapshots/Snapshot-Name.json +``` +{fixtureFolder}/---.json +{fixtureFolder}////.json ``` -You can also pass a "Step Name" to the Function +If a step wasn't named, it will instead use the ``for the file name, though this means that you will not be able to have more than 1 Snapshot in your It Block, as it would overwrite the previously created Snapshot files. -```js -cy.get(...).snapshot("Intercepted API Request") -// will save as -// cypress/snapshots/---Intercepted-API-Request.json -// to prevent duplications -``` -or both - -```js -cy.get(...).snapshot("Intercepted API Request", { - snapshotPath: "cypress/snapshots/api", - snapshotName: "first_intercept" -}) - -// will save as -// cypress/snapshots/api/first_intercept.json -``` - -### Configuration - -This module provides some configuration options: - -#### snapshot.snapshotPath - -Sets the default Path for saving Snapshots (default: `cypress/snapshots`) - -![Config Screenshot](./.github/assets/config.png) - -#### `ENV` CYPRESS_UPDATE_SNAPSHOTS - -Lets you pass a Env Variable to update failing Tests with the new Data - -# - -### Small print - -Authors: - -- Joshua <[data@shard.wtf](mailto:data@shard.wtf)> -- Gleb Bahmutov <gleb@cypress.io> - -
-License: MIT - do anything with the code, but don't blame us if it does not work. - -Support: If you find any problems with this module [open an issue](https://github.com/datashard/snapshot/issues) on Github - -## MIT License - -Copyright (c) 2017-2022 Cypress.io <hello@cypress.io> & Joshua <data@shard.wtf> - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -[npm-icon]: https://nodei.co/npm/@datashard/snapshot.svg?downloads=true -[npm-url]: https://npmjs.org/package/@datashard/snapshot -[semantic-url]: https://github.com/semantic-release/semantic-release -[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg -[renovate-app]: https://renovateapp.com/ +Of course, if a value changed, it will no longer match the snapshot and throw an Error. +![](./.github/assets/Error.png) \ No newline at end of file diff --git a/cypress.config.js b/cypress.config.js index 9acf04d..b774daf 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -3,7 +3,7 @@ const { defineConfig } = require("cypress"); module.exports = defineConfig({ snapshot: { - updateSnapshots: true, + // updateSnapshots: true, useFolders: true, }, @@ -12,4 +12,4 @@ module.exports = defineConfig({ // implement node event listeners here }, }, -}); +}); \ No newline at end of file diff --git a/cypress/e2e/1.cy.js b/cypress/e2e/1.cy.js index a86374e..11a4807 100644 --- a/cypress/e2e/1.cy.js +++ b/cypress/e2e/1.cy.js @@ -48,13 +48,13 @@ describe("datashard/snapshot", () => { cy.wrap({ "status": 200, "response": { - "array": [0, 1, 2, "Three"], + "array": [0, 1, 2, "4"], "object": { - "with": "more details" + // "with": "more details" } - } - } - ).snapshot() + }, + "thisisnew": "wtf" + }).snapshot() }) it("works based on fixtures", () => { cy diff --git a/cypress/e2e/2.cy.js b/cypress/e2e/2.cy.js index 9d4abde..ebd6fd7 100644 --- a/cypress/e2e/2.cy.js +++ b/cypress/e2e/2.cy.js @@ -3,25 +3,21 @@ describe("Random Describe", () => { context("Random Context", () => { it("Random It", () => { - cy.fixture("File").snapshot("Fixture File", { - snapshotName: "Random Fixture File" - }); - // cy.fixture("File2").snapshot("Fixture File", + cy.wrap(42).snapshot("Numbers"); + cy.wrap("foo-bar").snapshot("Strings"); + cy.wrap([1, 2, 3]).snapshot("Arrays"); }); // it("works with numbers", () => { // console.log(cy.wrap(42)) - // cy.wrap(42).snapshot(); // }); // it("works with strings", () => { // console.log(cy.wrap("foo-bar")) - // cy.wrap("foo-bar").snapshot(); // }); // it("works with arrays", () => { // console.log(cy.wrap([1, 2, 3])) - // cy.wrap([1, 2, 3]).snapshot(); // }); }); }); diff --git a/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Arrays.json b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Arrays.json new file mode 100644 index 0000000..83589cc --- /dev/null +++ b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Arrays.json @@ -0,0 +1,7 @@ +{ + "data": [ + 1, + 2, + 3 + ] +} \ No newline at end of file diff --git a/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Numbers.json b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Numbers.json new file mode 100644 index 0000000..7941fc3 --- /dev/null +++ b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Numbers.json @@ -0,0 +1,3 @@ +{ + "data": 42 +} \ No newline at end of file diff --git a/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Strings.json b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Strings.json new file mode 100644 index 0000000..c36f355 --- /dev/null +++ b/cypress/fixtures/snapshots/Random-Describe/Random-Context/Random-It/Strings.json @@ -0,0 +1,3 @@ +{ + "data": "foo-bar" +} \ No newline at end of file diff --git a/cypress/fixtures/snapshots/datashard-snapshot/complex-types/works-with-more-complicated-Objects.json b/cypress/fixtures/snapshots/datashard-snapshot/complex-types/works-with-more-complicated-Objects.json index 1de1100..4c1e9ca 100644 --- a/cypress/fixtures/snapshots/datashard-snapshot/complex-types/works-with-more-complicated-Objects.json +++ b/cypress/fixtures/snapshots/datashard-snapshot/complex-types/works-with-more-complicated-Objects.json @@ -5,10 +5,9 @@ 0, 1, 2, - "Three" + "4" ], - "object": { - "with": "more details" - } - } + "object": {} + }, + "thisisnew": "wtf" } \ No newline at end of file diff --git a/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-based-on-fixtures.json b/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-based-on-fixtures.json deleted file mode 100644 index ac6239a..0000000 --- a/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-based-on-fixtures.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonapi": { - "version": "2.0" - }, - "included": [ - { - "type": "users", - "id": "2", - "attributes": { - "name": "Test" - } - } - ] -} \ No newline at end of file diff --git a/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-with-more-complicated-Objects.json b/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-with-more-complicated-Objects.json deleted file mode 100644 index 1de1100..0000000 --- a/cypress/fixtures/snapshots/datashard-snapshot/simple-types/works-with-more-complicated-Objects.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "status": 200, - "response": { - "array": [ - 0, - 1, - 2, - "Three" - ], - "object": { - "with": "more details" - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index 0be2c4e..87282a2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "unused-deps": "dependency-check --unused --no-dev . --entry src/index.js", "semantic-release": "semantic-release", "cypress:open": "cypress open", - "cypress:update": "cypress run --env SNAPSHOT_UPDATE=true", + "cypress:update": "cypress run --env updateSnapshots=true", "cypress:run": "cypress run" }, "devDependencies": { diff --git a/src/snapshot.js b/src/snapshot.js index e9f1997..8ffb7dd 100644 --- a/src/snapshot.js +++ b/src/snapshot.js @@ -11,10 +11,16 @@ const pickSerializer = (asJson, value) => { return identity; }; +/** + * + * @param {string} text + * @returns {string} + */ + +const parseTextToJSON = (text) => text.replace(/\| [✅➖➕⭕]/g, "").trim().replace(/(.*?),\s*(\}|])/g, "$1$2").replace(/},(?!")$/g, "}"); + const store_snapshot = (props = { value, name, raiser }) => { if (Cypress.env().updateSnapshots || Cypress.config('snapshot').updateSnapshots) { - cy.log(props.name) - console.log(props.name) cy.writeFile(`${props.name}.json`, JSON.stringify(props.value, null, 2)) } else { // TODO: Figure out how to replace the fixture folder name if people move it @@ -39,6 +45,7 @@ const set_snapshot = ({ snapshotName, serialized, value }) => { const raiser = ({ value, expected }) => { const result = compareValues({ expected, value }); + // console.log("Final Result", result.result) if ((!Cypress.env().updateSnapshots || !Cypress.config('snapshot').updateSnapshots) && !result.success) { devToolsLog = { ...devToolsLog, @@ -49,10 +56,13 @@ const set_snapshot = ({ snapshotName, serialized, value }) => { throw new Error( `Snapshot Difference found.\nPlease Update the Snapshot\n - - ${JSON.stringify(result.result.replaceAll(/[╺┿╳]/g, ""), null, 2)}` - ); + ${JSON.stringify( + JSON.parse(parseTextToJSON(result.result).replaceAll(/[╺┿╳]/g, "")), null, 2) + .replaceAll(" ", " ") + } + + `); } }; Cypress.log(options); diff --git a/src/utils/compareValues.js b/src/utils/compareValues.js index e9b0cf5..ce803cb 100644 --- a/src/utils/compareValues.js +++ b/src/utils/compareValues.js @@ -1,52 +1,20 @@ -const isNestedData = (expected, value) => { - return ( - expected && value && typeof expected == `object` && typeof value == `object` - ); -}; + +const containsDiffChars = str => ["╺", "┿", "╳"].some(emoji => str.includes(emoji)); +const isNestedData = (expected, value) => expected && value && typeof expected == 'object' && typeof value == 'object'; + const checkDataState = (expected, value) => { let result; - if (expected === value) { result = `| ✅ "${value}",`; - } else if (expected == undefined) { - result = `| ➖ "➖╺ ${JSON.stringify(expected)?.replaceAll('"', "'")}|${value?.replaceAll('"', "'")}",`; - } else if (value == undefined) { - result = `| ➕ "➕┿ ${JSON.stringify(expected)?.replaceAll('"', "'")}|${value?.replaceAll('"', "'")}",`; } else { - result = `| ⭕ "⭕╳ ${JSON.stringify(expected)?.replaceAll('"', "'")}|${value?.replaceAll('"', "'")}",`; + result = `| ⭕ "⭕╳ ${JSON.stringify(expected)?.replaceAll('"', "'")} | ${value?.replaceAll('"', "'")}",`; } return result; }; - -/** - * - * @param {string} text - * @returns {string} - */ - -function parseTextToJSON(text) { - const lines = - // JSON.stringify( - text - .replace(/\| [✅➖➕⭕]/g, "").trim() - .replace(/(.*?),\s*(\}|])/g, "$1$2") - // ) - return lines; - // return JSON.stringify(lines, null, 2); -} - -function containsDiffChars(str) { - const emojis = ["╺", "┿", "╳"]; - return emojis.some(emoji => str.includes(emoji)); -} - const compare = (expected, value) => { - if(value === undefined){ - throw new Error("Please provide Data to compare against.") - } let compareResult = ""; if (isNestedData(expected, value)) { @@ -62,6 +30,9 @@ const compare = (expected, value) => { dataY = expected; } + console.log('datax', dataX) + console.log('dataY', dataY) + dataX.forEach(function (item, index) { const resultset = compare(item, dataY[index]); compareResult += resultset.result; @@ -87,18 +58,14 @@ const compare = (expected, value) => { } else { compareResult = checkDataState(expected, value); } - let result = parseTextToJSON(compareResult).replace(/(},)$/g, `}`); - console.log("compareResult",compareResult) - console.log("result", result.replace(/(},)$/g, `}`)) - // let result = compareResult; try { return { - success: !containsDiffChars(result), - result, + success: !containsDiffChars(compareResult), + result: compareResult, }; } catch (error) { - return { success: false, result }; + return { success: false, result: compareResult }; } };