Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add input values #5

Merged
merged 6 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 116 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,100 +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 | 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 `<input title id class="stuff">`
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 | `<div title="(x) => !x">` becomes `<div title="[function]">`
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. `<input>` becomes `<input value="whatever">`. Not sure how to do this in Vue 3
stringifyObjects | No | Replaces `title="[object Object]"` with `title="{a:'asdf'}"`. Not sure if this is possible in Vue 3


## 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. `<input title id class="stuff">`.
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 `<div title="function () { return true; }">` or this `<div title="(x) => !x">` with this placeholder `<div title="[function]">`.
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. `<input title id class="stuff">`.
`addInputValues` | `true` | Display internal element value on `input`, `textarea`, and `select` fields. `<input>` becomes `<input value="'whatever'">`.
`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 `<div title="function () { return true; }">` or this `<div title="(x) => !x">` with this placeholder `<div title="[function]">`.
`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
<div id="header">
Expand All @@ -107,7 +77,8 @@ There are 3 formatting options:
</div>
```

**"None" Output:** (no formatting applied)

#### **"None" Output:** (no formatting applied)

```js
global.vueSnapshots = {
Expand All @@ -122,7 +93,8 @@ global.vueSnapshots = {
</div>
```

**"Diffable" Output:**

#### **"Diffable" Output:**

```js
global.vueSnapshots = {
Expand All @@ -149,7 +121,10 @@ global.vueSnapshots = {
</div>
```

**Custom Function Output:**
**Note:** `<a>` and `<pre>` do not mutate the white space in their inner text in the "diffable" setting. This is for correctness.


#### **Custom Function Output:**

```js
global.vueSnapshots = {
Expand All @@ -175,3 +150,56 @@ Custom function example output:
<UL ID="MAIN-LIST" CLASS="LIST"><LI><A CLASS="LINK" HREF="#">MY HTML</A></LI></UL>
</DIV>
```


## 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('<div data-test="example">Text</div>');
console.log(formatted);
//`<div>
// Text
//</div>`
```

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.
2 changes: 0 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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);
Expand Down
59 changes: 58 additions & 1 deletion src/cheerioManipulation.js
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -24,6 +28,34 @@ const cheerioize = function (html) {
return $;
};

/**
* Appends a value attribute to input, select, and textareas
* to show the current value of the element in the snapshot.
*
* <input>
* <input value="Hello World">
*
* @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?.findAll) === 'function'
) {
const inputSelectors = 'input, textarea, select';
const inputs = vueWrapper.findAll(inputSelectors);

if (inputs?.at && inputs.at(0)) {
$(inputSelectors).each(function (index, element) {
const input = inputs.at(index);
const value = input.element.value;
element.attribs.value = swapQuotes(stringify(value));
});
}
}
};

/**
* This removes data-v-1234abcd="" from your snapshots.
*
Expand Down Expand Up @@ -152,9 +184,34 @@ 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();
}

/**
* 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.
*
* `<div class=""></div>` becomes `<div class></div>`
*
* 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);
removeServerRenderedText($);
removeTestTokens($);
removeScopedStylesDataVIDAttributes($);
Expand Down
Loading