From ef5959ac1b2eee5735e2b81da14138413c50372b Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Mon, 7 Oct 2024 20:56:13 -0400 Subject: [PATCH 1/6] Add input values --- README.md | 17 +- index.js | 2 - src/cheerioManipulation.js | 44 ++++- src/helpers.js | 70 +++++++ src/loadOptions.js | 1 + src/stringManipulation.js | 4 +- src/vNodeManipulation.js | 3 - tests/mockComponents/SeveralInputs.vue | 31 +++ .../cheerioManipulation.test.js.snap | 7 + tests/unit/src/cheerioManipulation.test.js | 14 ++ tests/unit/src/helpers.test.js | 178 ++++++++++++++++++ 11 files changed, 355 insertions(+), 16 deletions(-) delete mode 100644 src/vNodeManipulation.js create mode 100644 tests/mockComponents/SeveralInputs.vue create mode 100644 tests/unit/src/helpers.test.js diff --git a/README.md b/README.md index dbb673b..9c79d00 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,15 @@ removeDataQa | Yes | Removes `data-qa="whatever"` from you removeDataCy | Yes | Removes `data-cy="whatever"` from your snapshots (Cypress) removeDataPw | Yes - **New** | Removes `data-pw="whatever"` from your snapshots (Playwright) removeServerRendered | Yes | Removes `data-server-rendered="true"` from your snapshots -sortAttributes | Eventually | Sorts the attributes inside HTML elements in the snapshot. May not be in first release of v4 -attributesToClear | Probably | Array of attribute strings to remove the values from. `['title', 'id']` produces `` -verbose | Probably | Logs to the console errors or other messages if true -removeClassTest | Maybe | Removes all CSS classes that start with "test", `class="test-whatever"` -removeIdTest | Maybe | Removes `id="test-whatever"` or `id="testWhatever"`from snapshots -clearInlineFunctions | Maybe | `
` becomes `
` -removeIstanbulComments | No | I cannot reproduce this issue anymore. Will add it back in if people run into it again. -addInputValues | No | Display form field value. `` becomes ``. Not sure how to do this in Vue 3 +sortAttributes | Yes | Sorts the attributes inside HTML elements in the snapshot. May not be in first release of v4 +attributesToClear | Yes | Array of attribute strings to remove the values from. `['title', 'id']` produces `` +verbose | Yes | Logs to the console errors or other messages if true +removeClassTest | Yes | Removes all CSS classes that start with "test", `class="test-whatever"` +removeIdTest | Yes | Removes `id="test-whatever"` or `id="testWhatever"`from snapshots +addInputValues | Yes | Display form field value. `` becomes ``. Not sure how to do this in Vue 3 +clearInlineFunctions | Yes | `
` becomes `
` stringifyObjects | No | Replaces `title="[object Object]"` with `title="{a:'asdf'}"`. Not sure if this is possible in Vue 3 +removeIstanbulComments | No | I cannot reproduce this issue anymore. Will add it back in if people run into it again. ## New planned features @@ -74,6 +74,7 @@ Setting | Default | Description :-- | :-- | :-- verbose | `true` | Logs to the console errors or other messages if true. **Strongly recommended** if using experimental features. attributesToClear | [] | Takes an array of attribute strings, like `['title', 'id']`, to remove the values from these attributes. ``. +addInputValues | `true` | Display internal element value on `input`, `textarea`, and `select` fields. `` becomes ``. sortAttributes | `true` | Sorts the attributes inside HTML elements in the snapshot. This helps make snapshot diffs easier to read. removeServerRendered | `true` | Removes `data-server-rendered="true"` from your snapshots if true. removeDataVId | `true` | Removes `data-v-1234abcd=""` from your snapshots. diff --git a/index.js b/index.js index 5c7b0f3..b86ad55 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ import { isHtmlString, isVueWrapper } from './src/helpers.js'; import { loadOptions } from './src/loadOptions.js'; -import { vNodeManipulation } from './src/vNodeManipulation.js'; import { stringManipulation } from './src/stringManipulation.js'; import { formatMarkup } from './src/formatMarkup.js'; @@ -25,7 +24,6 @@ const test = function (received) { const print = function (received) { loadOptions(); let html = received || ''; - html = vNodeManipulation(html); html = stringManipulation(html); return formatMarkup(html); diff --git a/src/cheerioManipulation.js b/src/cheerioManipulation.js index 3ef2566..e6b800f 100644 --- a/src/cheerioManipulation.js +++ b/src/cheerioManipulation.js @@ -1,6 +1,10 @@ import * as cheerio from 'cheerio'; import * as htmlparser2 from 'htmlparser2'; +import { + stringify, + swapQuotes +} from '@/helpers.js'; import { removeTestTokens } from '@/removeTestTokens.js'; /** @@ -24,6 +28,31 @@ const cheerioize = function (html) { return $; }; +/** + * Sets appends a data-value attribute to input, select, and textareas + * to show the current value of the element in the snapshot. + * + * + * + * + * @param {object} $ The markup as a cheerio object + * @param {object} vueWrapper The Vue-Test Utils mounted component wrapper + */ +const addInputValues = function ($, vueWrapper) { + if ( + globalThis.vueSnapshots?.addInputValues && + typeof(vueWrapper?.html) === 'function' + ) { + const inputSelectors = 'input, textarea, select'; + + $(inputSelectors).each(function (index, element) { + const input = vueWrapper.findAll(inputSelectors).at(index); + const value = input.element.value; + element.attribs.value = swapQuotes(stringify(value)); + }); + } +}; + /** * This removes data-v-1234abcd="" from your snapshots. * @@ -152,9 +181,22 @@ const sortAttributes = function ($) { } }; -export const cheerioManipulation = function (html) { +/** + * Applies desired DOM manipulations based on + * global.vueSnapshots settings for improved snapshots. + * + * @param {Object|string} vueWrapper Either the Vue-Test-Utils mounted component object, or a string of html. + * @return {string} String of manipulated HTML, ready for formatting. + */ +export const cheerioManipulation = function (vueWrapper) { + let html = vueWrapper; + if (typeof(vueWrapper?.html) === 'function') { + html = vueWrapper.html(); + } + const $ = cheerioize(html); + addInputValues($, vueWrapper); removeServerRenderedText($); removeTestTokens($); removeScopedStylesDataVIDAttributes($); diff --git a/src/helpers.js b/src/helpers.js index 0fcc4f3..2ed68dc 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -24,3 +24,73 @@ export const logger = function (message) { console.info('Vue 3 Snapshot Serializer: ' + message); } }; + +/** + * Swaps single and double quotes. + * + * 'Text' => "Text" + * "Text" => 'Text' + * + * @param {string} string Input + * @return {string} Swapped output + */ +export const swapQuotes = function (string) { + return string.replace(/['"]/g, function (match) { + return match === '"' ? '\'' : '"'; + }); +}; + +/** + * Same as JSON.stringify, but without quotes around object properties. + * + * @param {object} obj data to stringify + * @return {string} stringified string + */ +export const stringify = function (obj) { + if (obj === null) { + return 'null'; + } + if (obj === undefined) { + return 'undefined'; + } + if (Number.isNaN(obj)) { + return 'NaN'; + } + if (obj === Infinity) { + return 'Infinity'; + } + if (obj === -Infinity) { + return '-Infinity'; + } + if (typeof(obj) === 'number') { + return String(obj); + } + if (obj instanceof Error) { + return 'Error: ' + obj.message; + } + if (obj instanceof Set) { + return JSON.stringify([...obj]); + } + if (typeof(obj) === 'object' && typeof(obj.getTime) === 'function') { + if (Number.isNaN(obj.getTime())) { + return obj.toString(); // 'Invalid Date' + } else { + return obj.getTime() + ''; // '1583463154386' + } + } + if (typeof(obj) === 'function') { + return 'Function'; + } + if (typeof(obj) !== 'object' || Array.isArray(obj)) { + return JSON.stringify(obj) || ''; + } + + let props = Object + .keys(obj) + .map((key) => { + return key + ':' + stringify(obj[key]); + }) + .join(','); + + return '{' + props + '}'; +}; diff --git a/src/loadOptions.js b/src/loadOptions.js index 022546a..c8e85d7 100644 --- a/src/loadOptions.js +++ b/src/loadOptions.js @@ -2,6 +2,7 @@ import { logger } from '@/helpers.js'; export const booleanDefaults = { verbose: true, + addInputValues: true, sortAttributes: true, removeServerRendered: true, removeDataVId: true, diff --git a/src/stringManipulation.js b/src/stringManipulation.js index 99982ff..d75dd30 100644 --- a/src/stringManipulation.js +++ b/src/stringManipulation.js @@ -6,8 +6,8 @@ import { cheerioManipulation } from '@/cheerioManipulation.js'; * Multi-line * Containing HTML * - * @param {string} html The markup being serialized - * @return {string} Modified HTML string + * @param {string} html The markup being serialized + * @return {string} Modified HTML string */ function removeAllComments (html) { if (globalThis.vueSnapshots?.removeComments) { diff --git a/src/vNodeManipulation.js b/src/vNodeManipulation.js deleted file mode 100644 index 489d453..0000000 --- a/src/vNodeManipulation.js +++ /dev/null @@ -1,3 +0,0 @@ -export const vNodeManipulation = function (vueWrapper) { - return vueWrapper; -}; diff --git a/tests/mockComponents/SeveralInputs.vue b/tests/mockComponents/SeveralInputs.vue new file mode 100644 index 0000000..e2c225f --- /dev/null +++ b/tests/mockComponents/SeveralInputs.vue @@ -0,0 +1,31 @@ + + + diff --git a/tests/unit/src/__snapshots__/cheerioManipulation.test.js.snap b/tests/unit/src/__snapshots__/cheerioManipulation.test.js.snap index 1310cbf..ade6c5e 100644 --- a/tests/unit/src/__snapshots__/cheerioManipulation.test.js.snap +++ b/tests/unit/src/__snapshots__/cheerioManipulation.test.js.snap @@ -1,5 +1,12 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`Cheerio Manipulation > Add input values > Adds values into DOM 1`] = ` +"
" +`; + exports[`Cheerio Manipulation > InlineFunctions.vue > Functions kept 1`] = ` "

Classic

diff --git a/tests/unit/src/cheerioManipulation.test.js b/tests/unit/src/cheerioManipulation.test.js index 19062a3..142779d 100644 --- a/tests/unit/src/cheerioManipulation.test.js +++ b/tests/unit/src/cheerioManipulation.test.js @@ -4,6 +4,7 @@ import { cheerioManipulation } from '@/cheerioManipulation.js'; import DataVId from '@@/mockComponents/DataVId.vue'; import InlineFunctions from '@@/mockComponents/InlineFunctions.vue'; +import SeveralInputs from '@@/mockComponents/SeveralInputs.vue'; import SortAttributes from '@@/mockComponents/SortAttributes.vue'; describe('Cheerio Manipulation', () => { @@ -167,4 +168,17 @@ describe('Cheerio Manipulation', () => { .toMatchSnapshot(); }); }); + + describe('Add input values', () => { + test('Adds values into DOM', async () => { + globalThis.vueSnapshots.addInputValues = true; + + const wrapper = await mount(SeveralInputs); + + await wrapper.find('[data-test="button"]').trigger('click'); + + expect(cheerioManipulation(wrapper)) + .toMatchSnapshot(); + }); + }); }); diff --git a/tests/unit/src/helpers.test.js b/tests/unit/src/helpers.test.js new file mode 100644 index 0000000..870524a --- /dev/null +++ b/tests/unit/src/helpers.test.js @@ -0,0 +1,178 @@ +import { mount } from '@vue/test-utils'; + +import { + isHtmlString, + isVueWrapper, + logger, + stringify, + swapQuotes +} from '@/helpers.js'; + +describe('Helpers', () => { + const info = console.info; + + beforeEach(() => { + globalThis.vueSnapshots = {}; + console.info = vi.fn(); + }); + + afterEach(() => { + console.info = info; + }); + + describe('IsHtmlString', () => { + test('Value is HTML', () => { + expect(isHtmlString('
')) + .toEqual(true); + }); + + test('Value is not HTML', () => { + expect(isHtmlString('console.log(2);')) + .toEqual(false); + }); + + test('Value is not a string', () => { + expect(isHtmlString({ html: vi.fn() })) + .toEqual(false); + }); + }); + + describe('IsVueWrapper', () => { + test('Is a Vue component wrapper', () => { + const wrapper = mount({ template: '
' }); + + expect(isVueWrapper(wrapper)) + .toEqual(true); + }); + + test('Is an object, but not a Vue wrapper', () => { + expect(isVueWrapper({})) + .toEqual(false); + }); + + test('Is not a Vue wrapper', () => { + expect(isVueWrapper('
')) + .toEqual(false); + }); + }); + + describe('Logger', () => { + test('Skips logging if verbose false', () => { + globalThis.vueSnapshots.verbose = false; + logger('Text'); + + expect(console.info) + .not.toHaveBeenCalled(); + }); + + test('Logs when verbose true', () => { + globalThis.vueSnapshots.verbose = true; + logger('Text'); + + expect(console.info) + .toHaveBeenCalledWith('Vue 3 Snapshot Serializer: Text'); + }); + }); + + describe('Stringify', () => { + test('Null', () => { + expect(stringify(null)) + .toEqual('null'); + }); + + test('Undefined', () => { + expect(stringify(undefined)) + .toEqual('undefined'); + }); + + test('Not a number', () => { + expect(stringify(NaN)) + .toEqual('NaN'); + }); + + test('Infinity', () => { + expect(stringify(Infinity)) + .toEqual('Infinity'); + }); + + test('Negative Infinity', () => { + expect(stringify(-Infinity)) + .toEqual('-Infinity'); + }); + + test('Number', () => { + expect(stringify(7)) + .toEqual('7'); + }); + + test('Error', () => { + expect(stringify(new Error('Text'))) + .toEqual('Error: Text'); + }); + + test('Set', () => { + expect(stringify(new Set(['a', 'b', 'c']))) + .toEqual('["a","b","c"]'); + }); + + test('Invalid Date', () => { + expect(stringify(new Date({}))) + .toEqual('Invalid Date'); + }); + + test('Date', () => { + expect(stringify(new Date('2024-01-01'))) + .toEqual('1704067200000'); + }); + + test('Function', () => { + const fn = () => { + return 2; + }; + + expect(stringify(fn)) + .toEqual('Function'); + }); + + test('Empty Array', () => { + expect(stringify(new Array())) + .toEqual('[]'); + }); + + test('Array', () => { + expect(stringify(['a', 'b'])) + .toEqual('["a","b"]'); + }); + + test('Boolean', () => { + expect(stringify(true)) + .toEqual('true'); + }); + + test('Symbol', () => { + expect(stringify(Symbol())) + .toEqual(''); + }); + + test('Object', () => { + const obj = { + subValue: { + key: '2' + } + }; + + expect(stringify(obj)) + .toEqual('{subValue:{key:"2"}}'); + }); + }); + + describe('SwapQuotes', () => { + test('Swaps quotes', () => { + expect(swapQuotes('That\'s some "Text".')) + .toEqual('That"s some \'Text\'.'); + + expect(swapQuotes('{ "key": "value" }')) + .toEqual('{ \'key\': \'value\' }'); + }); + }); +}); From 83d7a6025f5912475deafdce26d2c0d252fcfe95 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Mon, 7 Oct 2024 22:19:52 -0400 Subject: [PATCH 2/6] Clean up README --- README.md | 205 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 116 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 9c79d00..3df1c1f 100644 --- a/README.md +++ b/README.md @@ -5,101 +5,70 @@ Vue 3 Snapshot Serialization for Vitest and Jest. This is the successor to [jest-serializer-vue-tjw](https://github.com/tjw-lint/jest-serializer-vue-tjw) (Vue 2, Jest, CJS). -## Plan - -1. New repo tech stack: - * ESM `import` - * Vite + Vitest + Vitest snapshots - * Vue 3 - * GHA - Linting/Unit tests -1. Settings will now be stored differently: - * Settings no longer stored in files (`package.json`, `vue.config.js`, `vite.config.js`, etc.) - * Instead `globalThis.vueSnapshots = {};` will be used for settings. - * This allows users to define settings in their `global.beforeEach()` in their settings file. - * Also makes it much easier to override these global settings when you have test-specific settings. - * Would be be nice to abstract the settings gathering from the serialization, so the serialization can be externalized. - * `serializeVue(htmlOrVueWrapper, settings);` - * Would allow E2E tooling to import and use this directly - * The library would need to clear this global setting after every run to prevent global object-mutation based test-bleed. -1. Migration guide -1. Once feature support reaches an acceptable point, update the old repo to point people to this one. - * Place deprecation warning - * Point to migration guide, maybe migration guide should just live in the old repo and be linked to from the new one? - - -## Planned API Support: - -This is mostly taken from `jest-serializer-vue-tjw`: - -Setting | In new version? | Description -:-- | :-- | :-- -formatting | Yes, may change | Formmating options object, including new "diffable html" options -removeDataVId | Yes | Removes `data-v-1234abcd=""` from your snapshots -removeComments | Yes | Removes all HTML comments -removeDataTest | Yes | Removes `data-test="whatever"` from your snapshots -removeDataTestid | Yes | Removes `data-testid="whatever"` from your snapshots -removeDataTestId | Yes | Removes `data-test-id="whatever"` from your snapshots -removeDataQa | Yes | Removes `data-qa="whatever"` from your snapshots -removeDataCy | Yes | Removes `data-cy="whatever"` from your snapshots (Cypress) -removeDataPw | Yes - **New** | Removes `data-pw="whatever"` from your snapshots (Playwright) -removeServerRendered | Yes | Removes `data-server-rendered="true"` from your snapshots -sortAttributes | Yes | Sorts the attributes inside HTML elements in the snapshot. May not be in first release of v4 -attributesToClear | Yes | Array of attribute strings to remove the values from. `['title', 'id']` produces `` -verbose | Yes | Logs to the console errors or other messages if true -removeClassTest | Yes | Removes all CSS classes that start with "test", `class="test-whatever"` -removeIdTest | Yes | Removes `id="test-whatever"` or `id="testWhatever"`from snapshots -addInputValues | Yes | Display form field value. `` becomes ``. Not sure how to do this in Vue 3 -clearInlineFunctions | Yes | `
` becomes `
` -stringifyObjects | No | Replaces `title="[object Object]"` with `title="{a:'asdf'}"`. Not sure if this is possible in Vue 3 -removeIstanbulComments | No | I cannot reproduce this issue anymore. Will add it back in if people run into it again. - - -## New planned features - -Not in `jest-serializer-vue` - -* Remove Playwright tokens (`data-pw="whatever`) -* Diffable HTML (See [#85](https://github.com/tjw-lint/jest-serializer-vue-tjw/issues/85)) -* Support for E2E tooling like Playwright (see [#70](https://github.com/tjw-lint/jest-serializer-vue-tjw/issues/70)) - - -* * * - - -## Implemented +## Usage + +1. `npm install --save-dev vue3-snapshot-serializer` +1. **Vitest:** + * In your `vite.config.js` or `vitest.config.js`: + ```js + import { defineConfig } from 'vite'; // or 'vitest' + + export default defineConfig({ + test: { + snapshotSerializers: [ + './node_modules/vue3-snapshot-serializer/index.js' + ] + } + }); + ``` +1. **Jest:** + * In your `package.json`, or Jest config file: + ```json + { + "jest": { + "snapshotSerializers": [ + "./node_modules/vue3-snapshot-serializer/index.js" + ] + } + } + ``` + + +## Features The following features are implemented in this library: -Setting | Default | Description -:-- | :-- | :-- -verbose | `true` | Logs to the console errors or other messages if true. **Strongly recommended** if using experimental features. -attributesToClear | [] | Takes an array of attribute strings, like `['title', 'id']`, to remove the values from these attributes. ``. -addInputValues | `true` | Display internal element value on `input`, `textarea`, and `select` fields. `` becomes ``. -sortAttributes | `true` | Sorts the attributes inside HTML elements in the snapshot. This helps make snapshot diffs easier to read. -removeServerRendered | `true` | Removes `data-server-rendered="true"` from your snapshots if true. -removeDataVId | `true` | Removes `data-v-1234abcd=""` from your snapshots. -removeDataTest | `true` | Removes `data-test="whatever"` from your snapshots if true. To also remove these from your production builds, [see here](https://github.com/cogor/vite-plugin-vue-remove-attributes). -removeDataTestid | `true` | Removes `data-testid="whatever"` from your snapshots if true. -removeDataTestId | `true` | Removes `data-test-id="whatever"` from your snapshots if true. -removeDataQa | `false` | Removes `data-qa="whatever"` from your snapshots if true. `data-qa` is usually used by non-dev QA members. If they change in your snapshot, that indicates it may break someone else's E2E tests. So most using `data-qa` prefer they be left in by default. -removeDataCy | `false` | Removes `data-cy="whatever"` from your snapshots if true. `data-cy` is used by Cypress end-to-end tests. If they change in your snapshot, that indicates it may break an E2E tests. So most using `data-cy` prefer they be left in by default. -removeDataPw | `false` | Removes `data-pw="whatever"` from your snapshots if true. `data-pw` is used by Playwright end-to-end tests. If they change in your snapshot, that indicates it may break an E2E tests. So most using `data-pw` prefer they be left in by default. -removeIdTest | `false` | Removes `id="test-whatever"` or `id="testWhatever"`from snapshots. **Warning:** You should never use ID's for test tokens, as they can also be used by JS and CSS, making them more brittle. Use `data-test-id` instead. -removeClassTest | `false` | Removes all CSS classes that start with "test", `class="test-whatever"`. **Warning:** Don't use this approach. Use `data-test` instead. It is better suited for this because it doesn't conflate CSS and test tokens. -removeComments | `false` | Removes all HTML comments from your snapshots. This is false by default, as sometimes these comments can infer important information about how your DOM was rendered. However, this is mostly just personal preference. -clearInlineFunctions | `false` | Replaces `
` or this `
` with this placeholder `
`. -formatting | `'diffable'` | Function to use for formatting the markup output. See examples below. Accepts `'none'`, `'diffable'`, or a custom function handed a string of markup and must return a string. - - -## Formatting examples: +Setting | Default | Description +:-- | :-- | :-- +`verbose` | `true` | Logs to the console errors or other messages if true. +`attributesToClear` | [] | Takes an array of attribute strings, like `['title', 'id']`, to remove the values from these attributes. ``. +`addInputValues` | `true` | Display internal element value on `input`, `textarea`, and `select` fields. `` becomes ``. +`sortAttributes` | `true` | Sorts the attributes inside HTML elements in the snapshot. This helps make snapshot diffs easier to read. +`removeServerRendered` | `true` | Removes `data-server-rendered="true"` from your snapshots if true. +`removeDataVId` | `true` | Removes `data-v-1234abcd=""` from your snapshots if true. +`removeDataTest` | `true` | Removes `data-test="whatever"` from your snapshots if true. To also remove these from your production builds, [see here](https://github.com/cogor/vite-plugin-vue-remove-attributes). +`removeDataTestid` | `true` | Removes `data-testid="whatever"` from your snapshots if true. +`removeDataTestId` | `true` | Removes `data-test-id="whatever"` from your snapshots if true. +`removeDataQa` | `false` | Removes `data-qa="whatever"` from your snapshots if true. `data-qa` is usually used by non-dev QA members. If they change in your snapshot, that indicates it may break someone else's E2E tests. So most using `data-qa` prefer they be left in by default. +`removeDataCy` | `false` | Removes `data-cy="whatever"` from your snapshots if true. `data-cy` is used by Cypress end-to-end tests. If they change in your snapshot, that indicates it may break an E2E test. So most using `data-cy` prefer they be left in by default. +`removeDataPw` | `false` | Removes `data-pw="whatever"` from your snapshots if true. `data-pw` is used by Playwright end-to-end tests. If they change in your snapshot, that indicates it may break an E2E test. So most using `data-pw` prefer they be left in by default. +`removeIdTest` | `false` | Removes `id="test-whatever"` or `id="testWhatever"`from snapshots. **Warning:** You should never use ID's for test tokens, as they can also be used by JS and CSS, making them more brittle and their intent less clear. Use `data-test-id` instead. +`removeClassTest` | `false` | Removes all CSS classes that start with "test", like `class="test-whatever"`. **Warning:** Don't use this approach. Use `data-test` instead. It is better suited for this because it doesn't conflate CSS and test tokens. +`removeComments` | `false` | Removes all HTML comments from your snapshots. This is false by default, as sometimes these comments can infer important information about how your DOM was rendered. However, this is mostly just personal preference. +`clearInlineFunctions` | `false` | Replaces `
` or this `
` with this placeholder `
`. +`formatting` | `'diffable'` | Function to use for formatting the markup output. See examples below. Accepts `'none'`, `'diffable'`, or a function. + + +### Formatting examples: There are 3 formatting options: * None - does not apply any additional formatting +* Diffable - Applies formatting designed for more easily readble diffs * Custom function - You can pass in your own function to format the markup. -* Diffable - Applies formatting designed for more easily readble diffs (example below) -**Input:** + +#### **Input Example:** ```html ``` -**"None" Output:** (no formatting applied) + +#### **"None" Output:** (no formatting applied) ```js global.vueSnapshots = { @@ -123,7 +93,8 @@ global.vueSnapshots = {
``` -**"Diffable" Output:** + +#### **"Diffable" Output:** ```js global.vueSnapshots = { @@ -150,7 +121,10 @@ global.vueSnapshots = {
``` -**Custom Function Output:** +**Note:** `` and `
` do not mutate the white space in their inner text in the "diffable" setting. This is for correctness.
+
+
+#### **Custom Function Output:**
 
 ```js
 global.vueSnapshots = {
@@ -176,3 +150,56 @@ Custom function example output:
   
 
``` + + +## Adjusting settings + +In your `setup.js` file, I would recommend creating + +```js +global.beforeEach(() => { + global.vueSnapshots = { + // Your custom settings, such as: + verbose: true + }; +}); +``` + +With this in place, your snapshot settings will be reset before each test runs. This means you can freely override these settings in specific tests, like so: + +```js +import { mount } from '@vue/test-utils'; + +import MyComponent from '@/components/MyComponent.vue'; + +describe('MyComponent', () => { + test('My test', () => { + // Test-specific settings + global.vueSnapshots.attributesToClear = ['data-uuid']; + + expect(MyComponent) + .toMatchSnapshot(); + }); +}); +``` + + +## Using this library outside of Vitest/Jest + +This library has many great features for formatting and cleaning up markup. For example, you may want to create your own function to validate expected markup in an End-to-End (E2E) testing tool, like Playwright or Cypress. + +```js +import { vueMarkupFormatter } from 'vue3-snapshot-serializer'; + +globalThis.vueSnapshots = { + // Your settings +}; + +const formatted = vueMarkupFormatter('
Text
'); +console.log(formatted); +//`
+// Text +//
` +``` + +The `vueMarkupFormatter` function expects a string starting with `<`, and will return a formatted string based on your `globalThis.vueSnapshots` settings. You can use `global`, `globalThis`, or `window` to set the `vueSnapshots` settings object depending on your JavaScript environment. From c7cb982c8cd5b7370553c4360e689118d8566c6b Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Wed, 9 Oct 2024 14:56:15 -0400 Subject: [PATCH 3/6] Update src/cheerioManipulation.js --- src/cheerioManipulation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cheerioManipulation.js b/src/cheerioManipulation.js index e6b800f..f63c2ab 100644 --- a/src/cheerioManipulation.js +++ b/src/cheerioManipulation.js @@ -29,7 +29,7 @@ const cheerioize = function (html) { }; /** - * Sets appends a data-value attribute to input, select, and textareas + * Appends a value attribute to input, select, and textareas * to show the current value of the element in the snapshot. * * From d54f7ef8b6fde7ea766032acb2b6818f39af63dd Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Wed, 9 Oct 2024 15:06:27 -0400 Subject: [PATCH 4/6] Safer and more efficient --- src/cheerioManipulation.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/cheerioManipulation.js b/src/cheerioManipulation.js index f63c2ab..69f0508 100644 --- a/src/cheerioManipulation.js +++ b/src/cheerioManipulation.js @@ -41,15 +41,18 @@ const cheerioize = function (html) { const addInputValues = function ($, vueWrapper) { if ( globalThis.vueSnapshots?.addInputValues && - typeof(vueWrapper?.html) === 'function' + typeof(vueWrapper?.findAll) === 'function' ) { const inputSelectors = 'input, textarea, select'; + const inputs = vueWrapper.findAll(inputSelectors); - $(inputSelectors).each(function (index, element) { - const input = vueWrapper.findAll(inputSelectors).at(index); - const value = input.element.value; - element.attribs.value = swapQuotes(stringify(value)); - }); + if (inputs.at(0)) { + $(inputSelectors).each(function (index, element) { + const input = inputs.at(index); + const value = input.element.value; + element.attribs.value = swapQuotes(stringify(value)); + }); + } } }; From 4af5eb47af4d2fefbeea975199d63bbbf9bd62b7 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Wed, 9 Oct 2024 15:08:00 -0400 Subject: [PATCH 5/6] Safety check --- src/cheerioManipulation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cheerioManipulation.js b/src/cheerioManipulation.js index 69f0508..3217fea 100644 --- a/src/cheerioManipulation.js +++ b/src/cheerioManipulation.js @@ -46,7 +46,7 @@ const addInputValues = function ($, vueWrapper) { const inputSelectors = 'input, textarea, select'; const inputs = vueWrapper.findAll(inputSelectors); - if (inputs.at(0)) { + if (inputs?.at && inputs.at(0)) { $(inputSelectors).each(function (index, element) { const input = inputs.at(index); const value = input.element.value; From f0a96eca2a0bcbbde9f19564833447a3af1d56a4 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Wed, 9 Oct 2024 15:25:53 -0400 Subject: [PATCH 6/6] Comment --- src/cheerioManipulation.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cheerioManipulation.js b/src/cheerioManipulation.js index 3217fea..9d3f63d 100644 --- a/src/cheerioManipulation.js +++ b/src/cheerioManipulation.js @@ -197,6 +197,18 @@ export const cheerioManipulation = function (vueWrapper) { html = vueWrapper.html(); } + /** + * NOTE: Although we could check the settings and potentially skip + * the cheerioze step completely, that would result in inconsistent + * snapshots, as Cheerio removes empty attribute assignments. + * + * `
` becomes `
` + * + * Because of this, we should always pass the markup through Cheerio + * to keep all snapshots consistent, even if we are not doing any + * DOM manipulation. + */ + const $ = cheerioize(html); addInputValues($, vueWrapper);