├── .babelrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bootstrap-documentation-generation.js ├── bootstrap-unexpected-markdown.js ├── documentation ├── assertions │ ├── PreactElement │ │ ├── to-render-as.md │ │ └── when-rendered.md │ └── RenderedPreactElement │ │ ├── queried-for.md │ │ ├── to-contain.md │ │ ├── to-have-rendered.md │ │ ├── to-match-snapshot.md │ │ ├── to-satisfy-snapshot.md │ │ └── with-event.md └── index.md ├── jest-integration ├── baseTemplate │ ├── package.json │ ├── src │ │ ├── ClickCounter.js │ │ └── __tests__ │ │ │ └── ClickCounter.spec.js │ ├── testRunOutput.json │ ├── unexpected-preact-non-jest.js │ └── unexpected-preact.js ├── compare-json.js ├── jest-normalizer.js ├── normalize-jest-json.js ├── run-with-version.sh ├── run.sh ├── tests │ ├── basics │ │ ├── 1-create-initial-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ ├── 2-validate-existing-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ ├── 3-change-in-code │ │ │ ├── ClickCounter.spec.js │ │ │ ├── expectedOutput.json │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 4-update-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ └── 5-revalidate-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ ├── cleanup │ │ ├── 1-create-initial-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ ├── 2-remove-test-and-update │ │ │ ├── ClickCounter.spec.js │ │ │ ├── expected_stderr │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 3-add-test-back-with-different-snapshot │ │ │ ├── ClickCounter.spec.js │ │ │ ├── expected_stderr │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ └── 4-revalidate-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ ├── functions │ │ ├── 1-create-initial-snapshots │ │ │ ├── ClickCounter.spec.js │ │ │ ├── expected_stderr │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 2-remove-event-binding │ │ │ ├── ClickCounter.js │ │ │ ├── expectedOutput.json │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 3-update-without-binding │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ ├── 4-add-binding-back │ │ │ ├── ClickCounter.js │ │ │ ├── expectedOutput.json │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 5-update-with-binding │ │ │ ├── expected_stderr │ │ │ └── run.sh │ │ └── 6-revalidate-new-snapshots │ │ │ ├── expected_stderr │ │ │ └── run.sh │ ├── multiple │ │ ├── 1-create-initial-snapshots │ │ │ ├── ClickCounter-other.spec.js │ │ │ ├── expectedOutput.json │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 2-validate-existing-snapshots │ │ │ ├── expectedOutput.json │ │ │ └── run.sh │ │ ├── 3-change-in-code │ │ │ ├── ClickCounter-other.spec.js │ │ │ ├── ClickCounter.spec.js │ │ │ ├── expectedOutput.json │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ ├── 4-update-snapshots │ │ │ ├── expectedOutput.json │ │ │ ├── no_expectation │ │ │ └── run.sh │ │ └── 5-revalidate-snapshots │ │ │ ├── expectedOutput.json │ │ │ └── run.sh │ ├── preact-general │ │ ├── 1-preact │ │ │ ├── Components.js │ │ │ ├── expectedOutput.json │ │ │ ├── preact.spec.js │ │ │ ├── prepare.sh │ │ │ └── run.sh │ │ └── 2-preact-compat │ │ │ ├── Components.js │ │ │ ├── expectedOutput.json │ │ │ ├── preact.spec.js │ │ │ ├── prepare.sh │ │ │ └── run.sh │ └── wrong-require │ │ └── 1-failures │ │ ├── ClickCounter.spec.js │ │ ├── expectedOutput.json │ │ ├── prepare.sh │ │ └── run.sh └── update-test.sh ├── jest.js ├── package-lock.json ├── package.json ├── src ├── assertions │ ├── AssertionGenerator.js │ ├── deepAgainstRawAssertions.js │ ├── deepAssertions.js │ ├── jestSnapshotStandardRendererAssertions.js │ └── snapshotFunctionAssertions.js ├── helpers │ ├── snapshotLoader.js │ └── snapshots.js ├── jest.js ├── tests │ ├── components │ │ ├── ClickCounter.js │ │ └── Clicker.js │ ├── fixtures │ │ ├── functions.js │ │ ├── multiple.snapshot │ │ ├── multipleclasses.snapshot │ │ ├── single.snapshot │ │ └── twoclicks.snapshot │ ├── helpers │ │ ├── emulateDom.js │ │ └── mock-jasmine.js │ ├── snapshot.spec.js │ └── unexpected-preact-deep.spec.js ├── types │ ├── snapshotFunctionType.js │ └── types.js └── unexpected-preact.js ├── wallaby.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ], 3 | "plugins": [ 4 | ["transform-react-jsx", { "pragma": "h" } ], 5 | "transform-object-rest-spread" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | site-build/ 4 | jest-integration/build/ 5 | jest-integration/output/ 6 | jest-integration/baseTemplate/yarn.lock 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | wallaby.js 3 | wallaby-testrenderer.js 4 | site-build/ 5 | site-reactelement/ 6 | site-renderedreactelement/ 7 | site-testrenderer/ 8 | jest-integration/ 9 | demo/ 10 | src/tests/ 11 | lib/tests/ 12 | .travis.yml 13 | .coveralls.yml 14 | .eslintrc 15 | npm-debug.log 16 | yarn.lock 17 | bootstrap-documentation-generation.js 18 | bootstrap-documentation-generation-testrenderer.js 19 | bootstrap-unexpected-markdown.js 20 | bootstrap-unexpected-markdown-test-renderer.js 21 | gulpfile.js 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | v2.0.4 3 | * Update unexpected-htmllike-preactrendered-adapter to support elements created in `dangerouslySetInnerHTML` 4 | * Fix bug with `'to contain '` on pending-event type (thanks @salomvary for reporting) 5 | 6 | v2.0.3 7 | * No package changes, just add a missing repo link in package.json 8 | 9 | v2.0.2 10 | * Fix for `'queried for'` followed by `'with event'` 11 | * Fix for triggering events with incorrectly cased event names - the case is now automatically corrected 12 | * Integration tests added - now integration tested with preact 6, 7 and 8 13 | 14 | v2.0.1 15 | * Bump for new README and docs 16 | 17 | v2.0.0 18 | * (breaking) Make the returned promise from `expect` always return the component instance if a component was located (either via rendering or a sub-component via `'queried for'`). This keeps better compatibility with unexpected-react, and means there is no difference between preact and preact-compat rendered components. 19 | * Fix remaining preact-compat issues 20 | v1.0.2 21 | * Fix some preact-compat incompatibilities 22 | 23 | v1.0.1 24 | * Initial version, basics working with standard preact 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dave Brotherstone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unexpected-preact 2 | 3 | The [preact.js](http://preactjs.com) version of [unexpected-react](http://bruderstein.github.io/unexpected-react) 4 | 5 | ## Installation 6 | 7 | `npm install --save-dev unexpected unexpected-preact` 8 | 9 | ## Usage 10 | 11 | ES6: 12 | ```js 13 | import unexpected from 'unexpected'; 14 | import unexpectedPreact from 'unexpected-preact'; 15 | 16 | const expect = unexpected.clone().use(unexpectedPreact); 17 | ``` 18 | 19 | ES5 20 | ```js 21 | var unexpected = require('unexpected'); 22 | var unexpectedPreact = require('unexpected-preact'); 23 | 24 | var expect = unexpected.clone().use(unexpectedPreact); 25 | ``` 26 | 27 | **For Jest, require/import `unexpected-preact/jest`** 28 | 29 | With jest, you can use the snapshot test functionality - see the [`'to match snapshot'`](https://bruderstein.github.io/unexpected-preact/assertions/RenderedPreactElement/to-match-snapshot/) assertion 30 | 31 | Example test 32 | ```js 33 | it('increases the count on click', function () { 34 | expect(, 35 | 'when rendered', 36 | 'with event', 'click', 'on', 101 |
102 | 105 |
106 | 107 | ); 108 | } 109 | } 110 | 111 | global.TodoItem = TodoItem; 112 | global.TodoList = TodoList; 113 | global.App = App; 114 | 115 | class MyButton extends Preact.Component { 116 | constructor () { 117 | super(); 118 | this.state = { 119 | count: 0 120 | }; 121 | this.onClick = this.onClick.bind(this); 122 | } 123 | 124 | onClick () { 125 | const { count } = this.state; 126 | this.setState({ count: count + 1 }); 127 | } 128 | 129 | render() { 130 | const { count } = this.state; 131 | 132 | return ( 133 | 136 | ); 137 | } 138 | } 139 | 140 | global.MyButton = MyButton; 141 | -------------------------------------------------------------------------------- /documentation/assertions/PreactElement/to-render-as.md: -------------------------------------------------------------------------------- 1 | Given the following test component: 2 | 3 | ```js 4 | const MyComponent = () => ( 5 |
6 | one 7 | two 8 | three 9 |
10 | ) 11 | ``` 12 | 13 | You can validate the rendering of this component with the `to render as` assertion, 14 | which renders the component to a new DOM node, and validates the output. 15 | 16 | You can optionally use `'to deeply render as'`, which for Preact is identical, but does mean that the assertions will be compatible with unexpected-react. 17 | If you want the ability to switch to React and keep the same tests, it is advisable to use `'to deeply render as'`. 18 | 19 | ```js 20 | expect(, 'to render as',
); 21 | ``` 22 | 23 | This works using the same rules as `to have rendered`, but saves you creating a 24 | new DOM node when you just want to validate some translation of props to 25 | output. 26 | 27 | ```js 28 | // The span "two" is missing here, but it is ignored. 29 | expect(, 'to render as', 30 |
31 | one 32 | three 33 |
34 | ); 35 | ``` 36 | 37 | ```js 38 | // The following assertion will fail, as 'four' does not exist 39 | expect(, 'to render as', 40 |
41 | one 42 | four 43 |
44 | ); 45 | ``` 46 | 47 | ```output 48 | expected 49 | to render as
onefour
50 | 51 |
52 | one 53 | 54 | two // -two 55 | // +four 56 | 57 | three 58 |
59 | ``` 60 | 61 | If you want to check for an exact render, use `'to exactly render as'`. 62 | 63 | Alternatively, if you don't care about extra props, but want to check that there are no extra child nodes, use `'to have rendered with all children'` 64 | Note that `exactly` implies `with all children`, so you using both options is not necessary. 65 | 66 | Normally wrappers (elements that simply wrap other components) are ignored. This can be useful if you have higher order 67 | components wrapping your components, you can simply ignore them in your tests (they will be shown greyed out 68 | if there is a "real" error). If you want to test that there are no extra wrappers, simply add 69 | `with all wrappers` to the assertion. 70 | 71 | 72 | ```js 73 | // The span "two" is missing here, as is `className="parent"` 74 | // The missing span will cause an assertion error, but the extra prop will be ignored 75 | // due to `to have rendered with all children` being used 76 | 77 | expect(, 'to render with all children as', 78 |
79 | one 80 | three 81 |
82 | ); 83 | ``` 84 | 85 | ```output 86 | expected 87 | to render with all children as
onethree
88 | 89 |
90 | one 91 | two // should be removed 92 | three 93 |
94 | ``` 95 | 96 | ```js 97 | // The span "two" is missing here, as is `className="parent"` 98 | // This will cause an assertion error, 99 | // due to `to have exactly rendered` being used 100 | 101 | expect(, 'to exactly render as', 102 |
103 | one 104 | three 105 |
106 | ); 107 | ``` 108 | 109 | ```output 110 | expected 111 | to exactly render as
onethree
112 | 113 |
115 | one 116 | two // should be removed 117 | three 118 |
119 | ``` 120 | 121 | If you want to trigger events, use the `when rendered` assertion to render 122 | the component, then use the other `with event` and `to have rendered` or `to contain` 123 | assertions to validate the output. 124 | -------------------------------------------------------------------------------- /documentation/assertions/PreactElement/when-rendered.md: -------------------------------------------------------------------------------- 1 | 2 | You can render a component with the shallow renderer directly with the `when rendered` assertion. 3 | 4 | Say you have this component: 5 | ```js 6 | const Pages = (props) => { 7 | const pageElements = []; 8 | for (let i = 1; i <= props.count; ++i) { 9 | pageElements.push(Page {i}); 10 | } 11 | return ( 12 |
13 | {pageElements} 14 |
15 | ); 16 | }; 17 | 18 | ``` 19 | 20 | You can check the rendering directly using the `when rendered` assertion, saving you from creating the container DOM node by hand: 21 | ```js 22 | expect(, 23 | 'when rendered', 24 | 'to have rendered', 25 |
26 | Page 1 27 | Page 2 28 | Page 3 29 |
30 | ); 31 | ``` 32 | 33 | You can optionally use `'when deeply rendered'` which for Preact is identical, but maintains compatibility with unexpected-react. 34 | If you would like to be able to switch to React and keep the same tests, it is advisable to use `'when deeply rendered'` 35 | 36 | Everything works after the `when rendered` as normal, so you can trigger events and use the other assertions as you would otherwise. 37 | ```js 38 | expect(, 39 | 'when rendered', 40 | 'with event', 'click', 41 | 'to have rendered', 42 | 43 | ); 44 | ``` 45 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/queried-for.md: -------------------------------------------------------------------------------- 1 | This enables finding a particular component or element, to then perform further assertions on. 2 | 3 | e.g. 4 | ```js 5 | 6 | expect( 7 | 8 | 9 | 10 | 11 | , 12 | 'when rendered', 13 | 'queried for', 14 |
15 | {3} 16 |
, 17 | 'to have rendered', 18 |
19 | Buy groceries 20 |
21 | ); 22 | ``` 23 | 24 | Here we're searching for a `div` representing a todo item with the id 3. Because 25 | props are checked with `to satisfy`, extra data in this object is ignored. 26 | 27 | You can use `to have rendered` or `to contain` with all the options as usual following a `queried for`. 28 | 29 | It is also possible to extract the found component, by using the value of the returned promise from `expect`. 30 | 31 | 32 | ```js#async:true 33 | return expect( 34 | 35 | 36 | 37 | 38 | , 39 | 'when rendered', 40 | 'queried for', 41 | 42 | ).then(todoItem => { 43 | expect(todoItem.props.label, 'to equal', 'Buy groceries'); 44 | }); 45 | ``` 46 | 47 | ## queryTarget 48 | 49 | If you want to find a target nested inside a parent element, use `queryTarget` in the query. 50 | e.g. This `queried for` clause returns the `span` inside the `TodoItem` 51 | 52 | ```js 53 | expect( 54 | 55 | 56 | 57 | 58 | , 59 | 'queried for', 60 | 61 |
62 | 63 |
64 |
, 65 | 'to have rendered', 66 | Buy groceries 67 | ); 68 | ``` 69 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/to-contain.md: -------------------------------------------------------------------------------- 1 | It's possible to check for a part of the subtree, without 2 | testing the entire returned tree. This allows you to test specific elements, without 3 | writing brittle tests that break when the structure changes. 4 | 5 | Assuming the following component output 6 | ```js 7 | class MyComponent extends Component { 8 | render () { 9 | return ( 10 |
11 | one 12 | two 13 | three 14 |
15 | ); 16 | } 17 | }; 18 | 19 | ``` 20 | 21 | ```js 22 | // This will pass, as `two` can be found in the component's output 23 | expect(, 24 | 'when rendered', 25 | 'to contain', two); 26 | ``` 27 | 28 | Notice that the extra `className="middle"` in the `two` is ignored, 29 | in a similar way to the `to have rendered` assertion. 30 | 31 | You can override this behaviour by using `'to contain exactly'`, and `'to contain with all children'` 32 | 33 | 34 | ```js 35 | // This will fail, as `two` cannot be found in the renderers output, due to 36 | // the missing `className="middle"` prop 37 | expect(, 38 | 'when rendered', 39 | 'to contain exactly', two); 40 | ``` 41 | 42 | ```output 43 | expected 44 | 45 |
46 | one 47 | two 48 | three 49 |
50 |
51 | to contain exactly two 52 | 53 | the best match was 54 | 56 | two 57 | 58 | ``` 59 | 60 | The same thing applies to children for `'to contain'` as for `'to have rendered'`. 61 | 62 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/to-have-rendered.md: -------------------------------------------------------------------------------- 1 | Given the following test component: 2 | 3 | ```js 4 | class MyComponent extends Component { 5 | render () { 6 | return ( 7 |
8 | one 9 | two 10 | three 11 |
12 | ); 13 | } 14 | } 15 | ``` 16 | 17 | 18 | Extra props and children from the render are ignored 19 | ```js 20 | expect(, 21 | 'when rendered', 22 | 'to have rendered',
); 23 | ``` 24 | 25 | Note that `className` is always normalized to `class`, so you can use either. 26 | 27 | We've used the `'when rendered'` assertion to render the component, but you can also render it using `Preact.render()` and pass the return value 28 | 29 | ```js 30 | const container = document.createElement('div'); 31 | const node = Preact.render(, container); 32 | expect(node, 'to have rendered',
); 33 | ``` 34 | 35 | ```js 36 | // The span "two" is missing here, but it is ignored. 37 | expect(, 38 | 'when rendered', 39 | 'to have rendered', 40 |
41 | one 42 | three 43 |
44 | ); 45 | ``` 46 | 47 | ```js 48 | // The following assertion will fail, as 'four' does not exist 49 | expect(, 50 | 'when rendered', 51 | 'to have rendered', 52 |
53 | one 54 | four 55 |
56 | ); 57 | ``` 58 | 59 | ```output 60 | expected 61 | 62 |
63 | onetwothree 64 |
65 |
66 | to have rendered
onefour
67 | 68 |
69 | one 70 | 71 | two // -two 72 | // +four 73 | 74 | three 75 |
76 | ``` 77 | 78 | If you want to check for an exact render, use `'to have exactly rendered'`. 79 | 80 | Alternatively, if you don't care about extra props, but want to check that there are no extra child nodes, use `'to have rendered with all children'` 81 | Note that `exactly` implies `with all children`, so you using both options is not necessary. 82 | 83 | Normally wrappers (elements that simply wrap other components) are ignored. This can be useful if you have higher order 84 | components wrapping your components, you can simply ignore them in your tests (they will be shown greyed out 85 | if there is a "real" error). If you want to test that there are no extra wrappers, simply add 86 | `with all wrappers` to the assertion. 87 | 88 | 89 | ```js 90 | // The span "two" is missing here, as is `className="parent"` 91 | // The missing span will cause an assertion error, but the extra prop will be ignored 92 | // due to `to have rendered with all children` being used 93 | 94 | expect(, 95 | 'when rendered', 96 | 'to have rendered with all children', 97 |
98 | one 99 | three 100 |
101 | ); 102 | ``` 103 | 104 | ```output 105 | expected 106 | 107 |
108 | onetwothree 109 |
110 |
111 | to have rendered with all children
onethree
112 | 113 |
114 | one 115 | two // should be removed 116 | three 117 |
118 | ``` 119 | 120 | ```js 121 | // The span "two" is missing here, as is `className="parent"` 122 | // This will cause an assertion error, 123 | // due to `to have exactly rendered` being used 124 | 125 | expect(, 126 | 'when rendered', 127 | 'to have exactly rendered', 128 |
129 | one 130 | three 131 |
132 | ); 133 | ``` 134 | 135 | ```output 136 | expected 137 | 138 |
139 | onetwothree 140 |
141 |
142 | to have exactly rendered
onethree
143 | 144 |
146 | one 147 | two // should be removed 148 | three 149 |
150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/to-match-snapshot.md: -------------------------------------------------------------------------------- 1 | Under [jest](https://facebook.github.io/jest/), you can use snapshots. Snapshot tests save a snapshot of the component as it is currently rendered to a `.snapshot` file under a directory `__snapshots__`. Note that the snapshots for `unexpected-preact` are saved to a different filename than those jest uses natively. This is because the format differs slightly. 2 | 3 | Remember that to include snapshot support for the shallow and DOM renderers, you need to require unexpected-preact as `require('unexpected-preact/jest')` 4 | 5 | 6 | ```js 7 | class MyComponent extends Component { 8 | render () { 9 | return ( 10 |
11 | one 12 | two 13 | three 14 |
15 | ); 16 | } 17 | } 18 | ``` 19 | 20 | 21 | We can validate it matches the snapshot when it's rendered. If no snapshot exists, it will be automatically created the first time it is run. 22 | 23 | ```js#evaluate:false 24 | expect(, 'when rendered', 'to match snapshot'); 25 | ``` 26 | 27 | If in the future the component output changes, the error will be highlighted (using the same error highlighting used in the rest of unexpected-preact). 28 | 29 | Once you have checked that the changes are correct, you can run `jest -u` to update the snapshot, or if running in watch mode, press `u`. 30 | 31 | ### Events 32 | Triggered events still works, and can be combined with matching snaphots. 33 | 34 | e.g. 35 | 36 | ```js#evaluate:false 37 | 38 | expect(, 39 | 'when rendered', 40 | 'with event click', 41 | 'to match snapshot' 42 | ); 43 | ``` 44 | 45 | ### Matching 46 | 47 | The snapshot matches everything, so extra classes and attributes will causes a difference to be highlighted. If you want your snapshots to work more like `to have rendered`, so new attributes, classes and child elements can be added without triggering a change, see the assertion `to satisfy snapshot`. 48 | 49 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/to-satisfy-snapshot.md: -------------------------------------------------------------------------------- 1 | Under [jest](https://facebook.github.io/jest/), you can use snapshots. Snapshot tests save a snapshot of the component as it is currently rendered to a `.snapshot` file under a directory `__snapshots__`. Note that the snapshots for `unexpected-preact` are saved to a different filename than those jest uses natively. This is because the format differs slightly. 2 | 3 | Remember that to include snapshot support for the shallow and DOM renderers, you need to require unexpected-preact as `require('unexpected-preact/jest')` 4 | 5 | ```js 6 | class MyComponent extends Component { 7 | render () { 8 | return ( 9 |
10 | one 11 | two 12 | three 13 |
14 | ); 15 | } 16 | } 17 | ``` 18 | 19 | 20 | We can validate it satisfies the snapshot when it's rendered. If no snapshot exists, it will be automatically created the first time it is run. 21 | 22 | ```js#evaluate:false 23 | expect(, 'when rendered', 'to satisfy snapshot'); 24 | ``` 25 | 26 | If in the future the component output changes, the error will be highlighted (using the same error highlighting used in the rest of unexpected-react). 27 | 28 | Once you have checked that the changes are correct, you can run `jest -u` to update the snapshot, or if running in watch mode, press `u`. 29 | 30 | ### Events 31 | Triggered events still works, and can be combined with matching snaphots. 32 | 33 | e.g. 34 | 35 | ```js#evaluate:false 36 | 37 | expect(, 38 | 'when rendered', 39 | 'with event click', 40 | 'to satisfy snapshot' 41 | ); 42 | ``` 43 | 44 | ### Matching 45 | 46 | The snapshot matches in the same way as `to have rendered`, so new classes, attributes and child nodes are not treated as a difference. This can be advantageous when you want to add new features to a component, but expect the existing component to keep the same basic template. 47 | 48 | If you'd prefer to match the template exactly (order of classes is still ignored), see the assertion `to match snapshot`. 49 | -------------------------------------------------------------------------------- /documentation/assertions/RenderedPreactElement/with-event.md: -------------------------------------------------------------------------------- 1 | ## `with event` .... [`on`] 2 | 3 | `with event` can trigger events on your components. This is done by dispatching real browser events. 4 | 5 | e.g. with a button that counts it's own clicks 6 | 7 | ```js 8 | 9 | expect(, 10 | 'when rendered', 11 | 'with event', 'click', 12 | 'to have rendered', ); 13 | ``` 14 | 15 | If you want to trigger an event on a specific component, (i.e. not the top level component), use `on` 16 | after the event. 17 | 18 | ```js 19 | const todoList = ( 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | expect( 28 | todoList, 29 | 'when rendered', 30 | 'with event', 'click', 'on', , 31 | 'to contain', 32 |
33 | Buy groceries 34 |
35 | ); 36 | ``` 37 | 38 | To pass arguments to the event, simply include the event object after the event name. As real browser events are used, arguments must be supported by the event type. 39 | 40 | ```js 41 | expect( 42 | todoList, 43 | 'when rendered', 44 | 'with event mouseDown', { clientX: 150, clientY: 50 }, 'on', , 45 | 'to contain', 46 |
47 | Buy groceries 48 |
49 | ); 50 | ``` 51 | 52 | This will call the function passed in the `onMouseDown` prop of the ``. 53 | 54 | ## Multiple events 55 | 56 | To call multiple events, simple list them one after the other: 57 | 58 | 59 | ```js 60 | expect( 61 | todoList, 62 | 'when rendered', 63 | 'with event', 'click', { clientX: 150, clientY: 50 }, 'on', , 64 | 'with event', 'click', { clientX: 50, clientY: 50 }, 'on', , 65 | 'to have rendered', 66 |
67 | 68 |
69 | 70 | 71 |
72 | 73 |
74 | ); 75 | ``` 76 | 77 | You can optionally add `and` before the second and further events, to make it easier to read: 78 | 79 | ```js 80 | expect( 81 | todoList, 82 | 'when rendered', 83 | 'with event', 'click', { clientX: 150, clientY: 50 }, 'on', , 84 | 'and with event', 'click', { clientX: 50, clientY: 50 }, 'on', , 85 | 'to have rendered', 86 |
87 | 88 |
89 | 90 | 91 |
92 | 93 |
94 | ); 95 | ``` 96 | 97 | You can extract the renderer after an event by using the result of the promise returned from `expect` 98 | 99 | ```js#async:true 100 | return expect( 101 | todoList, 102 | 'when rendered', 103 | 'with event', 'mouseDown', { clientX: 150, clientY: 50 }, 'on', 104 | ).then(component => { 105 | expect( 106 | component, 107 | 'to contain', 108 | 109 |
110 | 111 | ); 112 | }); 113 | ``` 114 | 115 | ## eventTarget 116 | 117 | You can add an `eventTarget` prop to the expected to trigger the event on a child component. 118 | e.g. This will trigger the click in the ` 123 | ); 124 | }); 125 | }); 126 | ``` 127 | 128 | ### When using mocha 129 | 130 | Note: Unlike unexpected-react, the order of requires does not matter 131 | 132 | ```js#evaluate:false 133 | require( '../testHelpers/emulateDom'); 134 | 135 | var unexpected = require('unexpected'); 136 | var unexpectedPreact = require('unexpected-preact'); 137 | 138 | var MyComponent = require('../MyComponent); 139 | 140 | const expect = unexpected.clone() 141 | .use(unexpectedPreact); 142 | 143 | describe('MyComponent', function () { 144 | it('renders a button', function () { 145 | // All custom components and DOM elements are included in the tree, 146 | // so you can assert to whatever level you wish 147 | expect(, 148 | 'to render as', 149 | 150 | 151 | ); 152 | }); 153 | }); 154 | ``` 155 | 156 | ## Emulating the DOM 157 | 158 | ### When using jest 159 | 160 | For jest, this just works as jsdom is automatically configured. 161 | 162 | ### When using mocha 163 | 164 | The `emulateDom` file depends on whether you want to use [`domino`](https://npmjs.com/package/domino), or [`jsdom`](https://npmjs.com/package/jsdom) 165 | 166 | For `jsdom`: 167 | 168 | ```js#evaluate:false 169 | // emulateDom.js - jsdom variant 170 | 171 | if (typeof document === 'undefined') { 172 | 173 | const jsdom = require('jsdom').jsdom; 174 | global.document = jsdom(''); 175 | global.window = global.document.defaultView; 176 | 177 | for (let key in global.window) { 178 | if (!global[key]) { 179 | global[key] = global.window[key]; 180 | } 181 | } 182 | global.Node = global.window.Node; 183 | global.Text = global.window.Text; 184 | } 185 | ``` 186 | 187 | For `domino`: 188 | 189 | ```js#evaluate:false 190 | // emulateDom.js - domino variant 191 | 192 | if (typeof document === 'undefined') { 193 | 194 | const domino = require('domino'); 195 | global.window = domino.createWindow(''); 196 | global.document = global.window.document; 197 | global.navigator = { userAgent: 'domino' }; 198 | 199 | for (let key in global.window) { 200 | if (!global[key]) { 201 | global[key] = global.window[key]; 202 | } 203 | } 204 | } 205 | ``` 206 | 207 | # Preact Compatibility 208 | 209 | unexpected-preact is integration tested with preact version 6, 7 and 8. 210 | 211 | # Contributing 212 | 213 | We welcome pull requests, bug reports, and extra test cases. If you find something that doesn't work 214 | as you believe it should, or where the output isn't as good as it could be, raise an issue! 215 | 216 | ## Thanks 217 | 218 | [Unexpected](http://unexpected.js.org) is a great library to work with, and I offer my sincere thanks to [@sunesimonsen](https://github.com/sunesimonsen) 219 | and [@papandreou](https://github.com/papandreou), who have created an assertion library that makes testing JavaScript a joy. 220 | 221 | Thanks to [@oskarhane](https://twitter.com/oskarhane) for initially asking about this and helping with some early feedback. 222 | 223 | ## License 224 | MIT 225 | 226 | 227 | -------------------------------------------------------------------------------- /jest-integration/baseTemplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unexpected-preact-jest-integration-tests", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "preact": "8.x.x", 7 | "preact-compat": "^3.16.0" 8 | }, 9 | "devDependencies": { 10 | "jest": "*", 11 | "unexpected": "^10.22.2" 12 | }, 13 | "scripts": { 14 | "test": "jest --json --outputFile testRunOutput.json", 15 | "test-update": "jest --json --outputFile testRunOutput.json -u" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jest-integration/baseTemplate/src/ClickCounter.js: -------------------------------------------------------------------------------- 1 | import Preact, { h, Component } from 'preact'; 2 | 3 | export default class ClickCounter extends Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | this.onClick = this.onClick.bind(this); 9 | } 10 | 11 | onClick() { 12 | this.setState({ 13 | count: this.state.count + 1 14 | }); 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | ); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /jest-integration/baseTemplate/src/__tests__/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | it('counts a single click', function () { 16 | expect(, 17 | 'when rendered', 18 | 'with event', 'click', 'to match snapshot'); 19 | }); 20 | 21 | it('counts multiple clicks', function () { 22 | expect(, 23 | 'when rendered', 24 | 'with event', 'click', 25 | 'with event', 'click', 26 | 'with event', 'click', 27 | 'to match snapshot'); 28 | }); 29 | 30 | it('passes multiple snapshots in a single test', function () { 31 | expect(, 'when rendered', 'to match snapshot'); 32 | expect(, 33 | 'when rendered', 34 | 'with event', 'click', 35 | 'with event', 'click', 36 | 'with event', 'click', 37 | 'to match snapshot'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /jest-integration/baseTemplate/testRunOutput.json: -------------------------------------------------------------------------------- 1 | {"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":4,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTotalTestSuites":1,"numTotalTests":4,"snapshot":{"added":1,"failure":false,"filesAdded":1,"filesRemoved":0,"filesUnmatched":0,"filesUpdated":0,"matched":4,"total":5,"unchecked":0,"unmatched":0,"updated":0},"startTime":1493793327982,"success":true,"testResults":[{"assertionResults":[{"failureMessages":[],"status":"passed","title":"renders with default props"},{"failureMessages":[],"status":"passed","title":"counts a single click"},{"failureMessages":[],"status":"passed","title":"counts multiple clicks"},{"failureMessages":[],"status":"passed","title":"passes multiple snapshots in a single test"}],"endTime":1493793329496,"message":"","name":"/Users/davebrotherstone/work/personal/unexpected-preact/jest-integration/baseTemplate/src/__tests__/ClickCounter.spec.js","startTime":1493793328659,"status":"passed","summary":""}],"wasInterrupted":false} -------------------------------------------------------------------------------- /jest-integration/baseTemplate/unexpected-preact-non-jest.js: -------------------------------------------------------------------------------- 1 | if (!window.SVGElement) { 2 | window.SVGElement = function () {} 3 | } 4 | module.exports = require('unexpected-preact'); 5 | -------------------------------------------------------------------------------- /jest-integration/baseTemplate/unexpected-preact.js: -------------------------------------------------------------------------------- 1 | if (!window.SVGElement) { 2 | window.SVGElement = function () {} 3 | } 4 | module.exports = require('unexpected-preact/jest'); 5 | -------------------------------------------------------------------------------- /jest-integration/compare-json.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var normalizer = require('./jest-normalizer'); 3 | var jestJson = require(path.resolve(process.argv[2])); 4 | var expectedJson = require(path.resolve(process.argv[3])); 5 | var expect = require('unexpected'); 6 | 7 | try { 8 | expect(normalizer(jestJson, path.resolve(process.argv[4])), 'to satisfy', expectedJson); 9 | process.exit(0); 10 | } catch (error) { 11 | console.log(error.getDiff('text').toString()); 12 | process.exit(1); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /jest-integration/jest-normalizer.js: -------------------------------------------------------------------------------- 1 | var stackTraceRegex = /^\s+at [^\s]+\s\([^:]+:[0-9]+:[0-9]+\)/m; 2 | module.exports = function (jestJson, commonPath) { 3 | 4 | delete jestJson.startTime; 5 | jestJson.testResults.forEach(function (result) { 6 | 7 | delete result.startTime; 8 | delete result.endTime; 9 | result.assertionResults.forEach(function (test) { 10 | if (test.failureMessages) { 11 | test.failureMessages = test.failureMessages.map(function (msg) { 12 | var match = stackTraceRegex.exec(msg); 13 | if (match) { 14 | msg = msg.substr(0, match.index); 15 | } 16 | 17 | return msg; 18 | }); 19 | } 20 | }); 21 | 22 | result.assertionResults = result.assertionResults.sort(function (a, b) { 23 | if (a.title < b.title) { 24 | return -1; 25 | } else if (a.title > b.title) { 26 | return 1; 27 | } 28 | return 0; 29 | }); 30 | 31 | if (result.name.substr(0, commonPath.length) === commonPath) { 32 | result.name = result.name.substr(commonPath.length) 33 | } 34 | // Remove the stacktrace - often includes node internals which vary per version 35 | var match = stackTraceRegex.exec(result.message); 36 | if (match) { 37 | result.message = result.message.substr(0, match.index); 38 | } 39 | 40 | }); 41 | 42 | jestJson.testResults = jestJson.testResults.sort(function (a, b) { 43 | if (a.name < b.name) { 44 | return -1; 45 | } else if (a.name > b.name) { 46 | return 1; 47 | } 48 | return 0; 49 | }); 50 | 51 | return jestJson; 52 | } 53 | -------------------------------------------------------------------------------- /jest-integration/normalize-jest-json.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var jestJson = require(path.resolve(process.argv[2])); 3 | var normalizer = require('./jest-normalizer'); 4 | 5 | console.log(JSON.stringify(normalizer(jestJson, process.cwd()), null, 2)); 6 | -------------------------------------------------------------------------------- /jest-integration/run-with-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pushd . > /dev/null 3 | 4 | if [ $# -lt 1 ]; then 5 | echo "Must provide preact version to run with" 6 | echo "e.g. $0 8.x.x" 7 | exit 1 8 | fi 9 | 10 | USE_RELEASED_UNEXPECTED_REACT=no 11 | 12 | cd "$(dirname "$0")" 13 | 14 | cd baseTemplate 15 | rm -rf node_modules 16 | yarn install 17 | yarn upgrade preact@$1 18 | shift 19 | # install unexpected-preact locally 20 | npm install ../../ 21 | cd .. 22 | 23 | # Clean and prepare the build from the baseTemplate 24 | prepareBuild() { 25 | rm -rf build 26 | mkdir build 27 | cp -r baseTemplate/* build 28 | if [ $USE_RELEASED_UNEXPECTED_REACT == "yes" ]; then 29 | cd build 30 | ../useReleasedUnexpectedReact/prepare.sh ../useReleasedUnexpectedReact 31 | cd ..; 32 | fi 33 | } 34 | 35 | rm -rf output 36 | mkdir output 37 | 38 | FAILED_STEPS=0 39 | 40 | 41 | runTest() { 42 | TESTNAME=$1 43 | 44 | if [ -d "tests/$TESTNAME" ]; then 45 | prepareBuild 46 | for STEP in $(ls tests/$TESTNAME | sort -V); do 47 | STEP_FAILED=0 48 | if [ -d "tests/$TESTNAME/$STEP" ]; then 49 | if [ -f "tests/$TESTNAME/$STEP/prepare.sh" ]; then 50 | pushd . > /dev/null 51 | cd build 52 | source ../tests/$TESTNAME/$STEP/prepare.sh "../tests/$TESTNAME/$STEP"; 53 | popd > /dev/null 54 | fi 55 | 56 | if [ -f "tests/$TESTNAME/$STEP/run.sh" ]; then 57 | mkdir -p output/$TESTNAME/$STEP 58 | pushd . > /dev/null 59 | cd build 60 | printf "Running $TESTNAME $STEP..." 61 | source ../tests/$TESTNAME/$STEP/run.sh "../tests/$TESTNAME/$STEP" > ../output/${TESTNAME}/${STEP}/stdout 2> ../output/$TESTNAME/$STEP/stderr; 62 | 63 | if [ -f "testRunOutput.json" ]; then 64 | mv testRunOutput.json ../output/${TESTNAME}/${STEP}/ 65 | node ../normalize-jest-json.js ../output/${TESTNAME}/${STEP}/testRunOutput.json > ../output/${TESTNAME}/${STEP}/testRunOutput_clean.json 66 | fi 67 | 68 | popd > /dev/null 69 | 70 | if [ -f output/$TESTNAME/$STEP/stderr ]; then 71 | # Jest outputs a time for all tests on stdout, and the version number 72 | # we strip these out to allow a clean test 73 | # we do this whether there's expected stdout or not, so it's easy 74 | # to update the expected stdout 75 | cat output/$TESTNAME/$STEP/stdout | grep -v 'Done in [0-9.]' | grep -v -E 'yarn (run|test) v' > output/$TESTNAME/$STEP/stdout_clean; 76 | fi 77 | SOME_VALIDATION_WAS_MADE=false 78 | if [ -f output/${TESTNAME}/${STEP}/stdout_clean ] && [ -f tests/$TESTNAME/$STEP/expected_stdout ]; then 79 | SOME_VALIDATION_WAS_MADE=true 80 | diff tests/$TESTNAME/$STEP/expected_stdout output/$TESTNAME/$STEP/stdout_clean > output/$TESTNAME/$STEP/stdout_diff 81 | if [ $? == 1 ]; then 82 | echo "******************************************************************" >> output/results.txt 83 | echo "x Failed $TESTNAME $STEP - stdout differed" >> output/results.txt 84 | cat output/$TESTNAME/$STEP/stdout_diff >> output/results.txt 85 | echo "" >> output/results.txt; 86 | STEP_FAILED=1 87 | fi 88 | fi 89 | if [ -f output/$TESTNAME/$STEP/stderr ]; then 90 | # Jest outputs timings for each test and the complete times, 91 | # so we strip those to get a clean thing to compare 92 | # we do this whether there's expected stderr or not, so it's easy 93 | # to update the expected stderr 94 | cat output/$TESTNAME/$STEP/stderr | grep -v '^Time: ' | sed -E -e 's/\([0-9]+ms\)$//' > output/$TESTNAME/$STEP/stderr_clean; 95 | fi 96 | 97 | if [ -f output/${TESTNAME}/${STEP}/stderr_clean ] && [ -f tests/$TESTNAME/$STEP/expected_stderr ]; then 98 | SOME_VALIDATION_WAS_MADE=true 99 | diff tests/$TESTNAME/$STEP/expected_stderr output/$TESTNAME/$STEP/stderr_clean > output/$TESTNAME/$STEP/stderr_diff 100 | if [ $? == 1 ]; then 101 | echo "******************************************************************" >> output/results.txt 102 | echo "x Failed $TESTNAME $STEP - stderr differed" >> output/results.txt; 103 | cat output/$TESTNAME/$STEP/stderr_diff >> output/results.txt 104 | echo "" >> output/results.txt; 105 | STEP_FAILED=1 106 | fi 107 | fi 108 | 109 | if [ -f tests/$TESTNAME/$STEP/expectedOutput.json ]; then 110 | SOME_VALIDATION_WAS_MADE=true 111 | node compare-json.js output/$TESTNAME/$STEP/testRunOutput.json tests/$TESTNAME/$STEP/expectedOutput.json ./build > output/$TESTNAME/$STEP/json-diff-output.txt 112 | if [ $? == 1 ]; then 113 | echo "******************************************************************" >> output/results.txt 114 | echo "x Failed $TESTNAME $STEP - json differed" >> output/results.txt; 115 | cat output/$TESTNAME/$STEP/json-diff-output.txt >> output/results.txt 116 | echo "" >> output/results.txt; 117 | STEP_FAILED=1 118 | fi 119 | fi 120 | 121 | if [ "$SOME_VALIDATION_WAS_MADE" == "false" -a ! -f tests/$TESTNAME/$STEP/no_expectation ]; then 122 | echo "******************************************************************" >> output/results.txt 123 | echo "x Failed $TESTNAME $STEP - nothing was validated" >> output/results.txt; 124 | STEP_FAILED=1; 125 | fi 126 | FAILED_STEPS=$((FAILED_STEPS+STEP_FAILED)) 127 | if [ $STEP_FAILED -ne 0 ]; then 128 | printf "\rFAIL $TESTNAME $STEP \n"; 129 | else 130 | printf "\rPASS $TESTNAME $STEP \n"; 131 | fi 132 | fi 133 | fi; 134 | done 135 | fi; 136 | } 137 | 138 | TEST_ALL=true 139 | while [[ $# -ge 1 ]]; do 140 | key="$1" 141 | 142 | case $key in 143 | --use-released) 144 | USE_RELEASED_UNEXPECTED_REACT=yes 145 | ;; 146 | *) 147 | TEST_ALL=false 148 | runTest $key 149 | ;; 150 | esac 151 | shift # past argument or value 152 | done 153 | 154 | if [ "$TEST_ALL" == "true" ]; then 155 | for TESTNAME in $(ls tests | sort -V); do 156 | runTest $TESTNAME 157 | done; 158 | fi 159 | 160 | if [ $FAILED_STEPS -ne 0 ]; then 161 | cat output/results.txt 162 | echo "" 163 | echo "$FAILED_STEPS steps failed."; 164 | else 165 | echo "Passed!"; 166 | fi 167 | 168 | popd > /dev/null 169 | 170 | exit $FAILED_STEPS 171 | -------------------------------------------------------------------------------- /jest-integration/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./run-with-version.sh 8.x.x "$@" 4 | ./run-with-version.sh 7.x.x "$@" 5 | ./run-with-version.sh 6.x.x "$@" 6 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/1-create-initial-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Snapshot Summary 9 | › 5 snapshots written in 1 test suite. 10 | 11 | Test Suites: 1 passed, 1 total 12 | Tests: 4 passed, 4 total 13 | Snapshots: 5 added, 5 total 14 | Ran all test suites. 15 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/1-create-initial-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/2-validate-existing-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 4 passed, 4 total 10 | Snapshots: 5 passed, 5 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/2-validate-existing-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/3-change-in-code/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | it('counts a single click', function () { 16 | expect(, 17 | 'when rendered', 18 | 'with event', 'click', 'to match snapshot'); 19 | }); 20 | 21 | it('counts multiple clicks', function () { 22 | expect(, 23 | 'when rendered', 24 | 'with event', 'click', 25 | 'with event', 'click', 26 | 'with event', 'click', 27 | 'with event', 'click', 28 | 'to match snapshot'); 29 | }); 30 | 31 | it('passes multiple snapshots in a single test', function () { 32 | expect(, 'when rendered', 'to match snapshot'); 33 | expect(, 34 | 'when rendered', 35 | 'with event', 'click', 36 | 'with event', 'click', 37 | 'with event', 'click', 38 | 'to match snapshot'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/3-change-in-code/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 1, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 3, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 4, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": true, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 1, 17 | "filesUpdated": 0, 18 | "matched": 4, 19 | "total": 5, 20 | "unchecked": 0, 21 | "unmatched": 1, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [], 30 | "status": "passed", 31 | "title": "counts a single click" 32 | }, 33 | { 34 | "failureMessages": [ 35 | "UnexpectedError: \nexpected \nwhen rendered with event 'click', 'with event' click 'with event', 'click', 'with event', 'click', 'to match snapshot'\n\n\n" 36 | ], 37 | "status": "failed", 38 | "title": "counts multiple clicks" 39 | }, 40 | { 41 | "failureMessages": [], 42 | "status": "passed", 43 | "title": "passes multiple snapshots in a single test" 44 | }, 45 | { 46 | "failureMessages": [], 47 | "status": "passed", 48 | "title": "renders with default props" 49 | } 50 | ], 51 | "message": " ● ClickCounter › counts multiple clicks\n\n UnexpectedError: \n expected \n when rendered with event 'click', 'with event' click 'with event', 'click', 'with event', 'click', 'to match snapshot'\n \n \n", 52 | "name": "/src/__tests__/ClickCounter.spec.js", 53 | "status": "failed", 54 | "summary": "" 55 | } 56 | ], 57 | "wasInterrupted": false 58 | } 59 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/3-change-in-code/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/3-change-in-code/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/4-update-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Snapshot Summary 9 | › 1 snapshot updated in 1 test suite. 10 | 11 | Test Suites: 1 passed, 1 total 12 | Tests: 4 passed, 4 total 13 | Snapshots: 1 updated, 4 passed, 5 total 14 | Ran all test suites. 15 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/4-update-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test-update 4 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/5-revalidate-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 4 passed, 4 total 10 | Snapshots: 5 passed, 5 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/basics/5-revalidate-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/1-create-initial-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Snapshot Summary 9 | › 5 snapshots written in 1 test suite. 10 | 11 | Test Suites: 1 passed, 1 total 12 | Tests: 4 passed, 4 total 13 | Snapshots: 5 added, 5 total 14 | Ran all test suites. 15 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/1-create-initial-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/2-remove-test-and-update/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | it('counts multiple clicks', function () { 16 | expect(, 17 | 'when rendered', 18 | 'with event', 'click', 19 | 'with event', 'click', 20 | 'with event', 'click', 21 | 'to match snapshot'); 22 | }); 23 | 24 | it('passes multiple snapshots in a single test', function () { 25 | expect(, 'when rendered', 'to match snapshot'); 26 | expect(, 27 | 'when rendered', 28 | 'with event', 'click', 29 | 'with event', 'click', 30 | 'with event', 'click', 31 | 'to match snapshot'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/2-remove-test-and-update/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts multiple clicks 5 | ✓ passes multiple snapshots in a single test 6 | 7 | Test Suites: 1 passed, 1 total 8 | Tests: 3 passed, 3 total 9 | Snapshots: 4 passed, 4 total 10 | Ran all test suites. 11 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/2-remove-test-and-update/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/2-remove-test-and-update/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test-update 4 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/3-add-test-back-with-different-snapshot/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | it('counts a single click', function () { 16 | // This test is the one that has been added back, only this time we change the output 17 | // by adding an extra click - the test should still pass because it didn't exist on the last run, 18 | // so the snapshot should have been cleaned up 19 | expect(, 20 | 'when rendered', 21 | 'with event', 'click', 22 | 'with event', 'click', 23 | 'to match snapshot'); 24 | }); 25 | 26 | it('counts multiple clicks', function () { 27 | expect(, 28 | 'when rendered', 29 | 'with event', 'click', 30 | 'with event', 'click', 31 | 'with event', 'click', 32 | 'to match snapshot'); 33 | }); 34 | 35 | it('passes multiple snapshots in a single test', function () { 36 | expect(, 'when rendered', 'to match snapshot'); 37 | expect(, 38 | 'when rendered', 39 | 'with event', 'click', 40 | 'with event', 'click', 41 | 'with event', 'click', 42 | 'to match snapshot'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/3-add-test-back-with-different-snapshot/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Snapshot Summary 9 | › 1 snapshot written in 1 test suite. 10 | 11 | Test Suites: 1 passed, 1 total 12 | Tests: 4 passed, 4 total 13 | Snapshots: 1 added, 4 passed, 5 total 14 | Ran all test suites. 15 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/3-add-test-back-with-different-snapshot/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/3-add-test-back-with-different-snapshot/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/4-revalidate-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | ✓ counts a single click 5 | ✓ counts multiple clicks 6 | ✓ passes multiple snapshots in a single test 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 4 passed, 4 total 10 | Snapshots: 5 passed, 5 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/cleanup/4-revalidate-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/1-create-initial-snapshots/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | // We've removed all other tests, as the onClick won't work without the binding 16 | // but the point of this test it to check that the test notices the binding is gone 17 | }); 18 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/1-create-initial-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | 5 | Snapshot Summary 6 | › 1 snapshot written in 1 test suite. 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 1 passed, 1 total 10 | Snapshots: 1 added, 1 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/1-create-initial-snapshots/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/1-create-initial-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/2-remove-event-binding/ClickCounter.js: -------------------------------------------------------------------------------- 1 | import Preact, { Component, h } from 'preact'; 2 | 3 | export default class ClickCounter extends Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | // For this test, we remove the onClick this binding 9 | // this.onClick = this.onClick.bind(this); 10 | } 11 | 12 | onClick() { 13 | this.setState({ 14 | count: this.state.count + 1 15 | }); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | ); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/2-remove-event-binding/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 1, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 0, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 1, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": true, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 1, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 1, 20 | "unchecked": 0, 21 | "unmatched": 1, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [ 30 | "UnexpectedError: \nexpected when rendered to match snapshot\n\n\n Clicked 0 times\n\n" 31 | ], 32 | "status": "failed", 33 | "title": "renders with default props" 34 | } 35 | ], 36 | "message": " ● ClickCounter › renders with default props\n\n UnexpectedError: \n expected when rendered to match snapshot\n \n \n Clicked 0 times\n \n", 37 | "name": "/src/__tests__/ClickCounter.spec.js", 38 | "status": "failed", 39 | "summary": "" 40 | } 41 | ], 42 | "wasInterrupted": false 43 | } 44 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/2-remove-event-binding/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.js src 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/2-remove-event-binding/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/3-update-without-binding/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | 5 | Snapshot Summary 6 | › 1 snapshot updated in 1 test suite. 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 1 passed, 1 total 10 | Snapshots: 1 updated, 1 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/3-update-without-binding/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test-update 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/4-add-binding-back/ClickCounter.js: -------------------------------------------------------------------------------- 1 | import Preact, { Component, h } from 'preact'; 2 | 3 | export default class ClickCounter extends Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | // This time the binding is back 9 | this.onClick = this.onClick.bind(this); 10 | } 11 | 12 | onClick() { 13 | this.setState({ 14 | count: this.state.count + 1 15 | }); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | ); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/4-add-binding-back/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 1, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 0, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 1, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": true, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 1, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 1, 20 | "unchecked": 0, 21 | "unmatched": 1, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [ 30 | "UnexpectedError: \nexpected when rendered to match snapshot\n\n\n Clicked 0 times\n\n" 31 | ], 32 | "status": "failed", 33 | "title": "renders with default props" 34 | } 35 | ], 36 | "message": " ● ClickCounter › renders with default props\n\n UnexpectedError: \n expected when rendered to match snapshot\n \n \n Clicked 0 times\n \n", 37 | "name": "/src/__tests__/ClickCounter.spec.js", 38 | "status": "failed", 39 | "summary": "" 40 | } 41 | ], 42 | "wasInterrupted": false 43 | } 44 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/4-add-binding-back/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.js src 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/4-add-binding-back/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/5-update-with-binding/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | 5 | Snapshot Summary 6 | › 1 snapshot updated in 1 test suite. 7 | 8 | Test Suites: 1 passed, 1 total 9 | Tests: 1 passed, 1 total 10 | Snapshots: 1 updated, 1 total 11 | Ran all test suites. 12 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/5-update-with-binding/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test-update 4 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/6-revalidate-new-snapshots/expected_stderr: -------------------------------------------------------------------------------- 1 | PASS src/__tests__/ClickCounter.spec.js 2 | ClickCounter 3 | ✓ renders with default props 4 | 5 | Test Suites: 1 passed, 1 total 6 | Tests: 1 passed, 1 total 7 | Snapshots: 1 passed, 1 total 8 | Ran all test suites. 9 | -------------------------------------------------------------------------------- /jest-integration/tests/functions/6-revalidate-new-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/1-create-initial-snapshots/ClickCounter-other.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, {h} from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('counts a single click', function () { 12 | // this test has the same name as the test in the other main spec file 13 | // but this changes the output, to ensure that the snapshots are being stored 14 | // correctly per test file 15 | expect(, 16 | 'when rendered', 17 | 'with event click', 18 | 'with event click', 19 | 'to match snapshot'); 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/1-create-initial-snapshots/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 0, 3 | "numFailedTests": 0, 4 | "numPassedTestSuites": 2, 5 | "numPassedTests": 5, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 2, 10 | "numTotalTests": 5, 11 | "snapshot": { 12 | "added": 6, 13 | "failure": false, 14 | "filesAdded": 2, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 6, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": true, 25 | "testResults": [ 26 | { 27 | "message": "", 28 | "name": "/src/__tests__/ClickCounter-other.spec.js", 29 | "summary": "", 30 | "status": "passed", 31 | "assertionResults": [ 32 | { 33 | "status": "passed", 34 | "title": "counts a single click" 35 | } 36 | ] 37 | }, 38 | { 39 | "message": "", 40 | "name": "/src/__tests__/ClickCounter.spec.js", 41 | "summary": "", 42 | "status": "passed", 43 | "assertionResults": [ 44 | { 45 | "status": "passed", 46 | "title": "counts a single click" 47 | }, 48 | { 49 | "status": "passed", 50 | "title": "counts multiple clicks" 51 | }, 52 | { 53 | "status": "passed", 54 | "title": "passes multiple snapshots in a single test" 55 | }, 56 | { 57 | "status": "passed", 58 | "title": "renders with default props" 59 | } 60 | ] 61 | } 62 | ], 63 | "wasInterrupted": false 64 | } 65 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/1-create-initial-snapshots/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter-other.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/1-create-initial-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/2-validate-existing-snapshots/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 0, 3 | "numFailedTests": 0, 4 | "numPassedTestSuites": 2, 5 | "numPassedTests": 5, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 2, 10 | "numTotalTests": 5, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": false, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 6, 19 | "total": 6, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": true, 25 | "testResults": [ 26 | { 27 | "message": "", 28 | "name": "/src/__tests__/ClickCounter-other.spec.js", 29 | "summary": "", 30 | "status": "passed", 31 | "assertionResults": [ 32 | { 33 | "status": "passed", 34 | "title": "counts a single click" 35 | } 36 | ] 37 | }, 38 | { 39 | "message": "", 40 | "name": "/src/__tests__/ClickCounter.spec.js", 41 | "summary": "", 42 | "status": "passed", 43 | "assertionResults": [ 44 | { 45 | "status": "passed", 46 | "title": "counts a single click" 47 | }, 48 | { 49 | "status": "passed", 50 | "title": "counts multiple clicks" 51 | }, 52 | { 53 | "status": "passed", 54 | "title": "passes multiple snapshots in a single test" 55 | }, 56 | { 57 | "status": "passed", 58 | "title": "renders with default props" 59 | } 60 | ] 61 | } 62 | ], 63 | "wasInterrupted": false 64 | } 65 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/2-validate-existing-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/3-change-in-code/ClickCounter-other.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, {h} from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('counts a single click', function () { 12 | // this test has the same name as the test in the other main spec file 13 | // but this changes the output, to ensure that the snapshots are being stored 14 | // correctly per test file 15 | expect(, 16 | 'when rendered', 17 | 'with event click', 18 | 'with event click', 19 | 'with event click', 20 | 'to match snapshot'); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/3-change-in-code/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | expect(, 'when rendered', 'to match snapshot'); 13 | }); 14 | 15 | it('counts a single click', function () { 16 | expect(, 17 | 'when rendered', 18 | 'with event', 'click', 'to match snapshot'); 19 | }); 20 | 21 | it('counts multiple clicks', function () { 22 | expect(, 23 | 'when rendered', 24 | 'with event', 'click', 25 | 'with event', 'click', 26 | 'with event', 'click', 27 | 'with event', 'click', 28 | 'to match snapshot'); 29 | }); 30 | 31 | it('passes multiple snapshots in a single test', function () { 32 | expect(, 'when rendered', 'to match snapshot'); 33 | expect(, 34 | 'when rendered', 35 | 'with event', 'click', 36 | 'with event', 'click', 37 | 'with event', 'click', 38 | 'to match snapshot'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/3-change-in-code/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 2, 3 | "numFailedTests": 2, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 3, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 2, 10 | "numTotalTests": 5, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": true, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 2, 17 | "filesUpdated": 0, 18 | "matched": 4, 19 | "total": 6, 20 | "unchecked": 0, 21 | "unmatched": 2, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [ 30 | "UnexpectedError: \nexpected \nwhen rendered with event click 'with event click', 'with event click', 'to match snapshot'\n\n\n" 31 | ], 32 | "status": "failed", 33 | "title": "counts a single click" 34 | } 35 | ], 36 | "message": " ● ClickCounter › counts a single click\n\n UnexpectedError: \n expected \n when rendered with event click 'with event click', 'with event click', 'to match snapshot'\n \n \n", 37 | "name": "/src/__tests__/ClickCounter-other.spec.js", 38 | "status": "failed", 39 | "summary": "" 40 | }, 41 | { 42 | "assertionResults": [ 43 | { 44 | "failureMessages": [], 45 | "status": "passed", 46 | "title": "counts a single click" 47 | }, 48 | { 49 | "failureMessages": [ 50 | "UnexpectedError: \nexpected \nwhen rendered with event 'click', 'with event' click 'with event', 'click', 'with event', 'click', 'to match snapshot'\n\n\n" 51 | ], 52 | "status": "failed", 53 | "title": "counts multiple clicks" 54 | }, 55 | { 56 | "failureMessages": [], 57 | "status": "passed", 58 | "title": "passes multiple snapshots in a single test" 59 | }, 60 | { 61 | "failureMessages": [], 62 | "status": "passed", 63 | "title": "renders with default props" 64 | } 65 | ], 66 | "message": " ● ClickCounter › counts multiple clicks\n\n UnexpectedError: \n expected \n when rendered with event 'click', 'with event' click 'with event', 'click', 'with event', 'click', 'to match snapshot'\n \n \n", 67 | "name": "/src/__tests__/ClickCounter.spec.js", 68 | "status": "failed", 69 | "summary": "" 70 | } 71 | ], 72 | "wasInterrupted": false 73 | } 74 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/3-change-in-code/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | cp $1/ClickCounter-other.spec.js src/__tests__/ 5 | 6 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/3-change-in-code/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/4-update-snapshots/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 0, 3 | "numFailedTests": 0, 4 | "numPassedTestSuites": 2, 5 | "numPassedTests": 5, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 2, 10 | "numTotalTests": 5, 11 | "snapshot": { 12 | "added": 0, 13 | "didUpdate": true, 14 | "failure": false, 15 | "filesAdded": 0, 16 | "filesRemoved": 0, 17 | "filesUnmatched": 0, 18 | "filesUpdated": 2, 19 | "matched": 4, 20 | "total": 6, 21 | "unchecked": 0, 22 | "unmatched": 0, 23 | "updated": 2 24 | }, 25 | "success": true, 26 | "testResults": [ 27 | { 28 | "message": "", 29 | "name": "/src/__tests__/ClickCounter-other.spec.js", 30 | "summary": "", 31 | "status": "passed", 32 | "assertionResults": [ 33 | { 34 | "status": "passed", 35 | "title": "counts a single click" 36 | } 37 | ] 38 | }, 39 | { 40 | "message": "", 41 | "name": "/src/__tests__/ClickCounter.spec.js", 42 | "summary": "", 43 | "status": "passed", 44 | "assertionResults": [ 45 | { 46 | "status": "passed", 47 | "title": "counts a single click" 48 | }, 49 | { 50 | "status": "passed", 51 | "title": "counts multiple clicks" 52 | }, 53 | { 54 | "status": "passed", 55 | "title": "passes multiple snapshots in a single test" 56 | }, 57 | { 58 | "status": "passed", 59 | "title": "renders with default props" 60 | } 61 | ] 62 | } 63 | ], 64 | "wasInterrupted": false 65 | } 66 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/4-update-snapshots/no_expectation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/unexpected-preact/a672b07c3ab888421c4afd5d057965c6c6c57ad8/jest-integration/tests/multiple/4-update-snapshots/no_expectation -------------------------------------------------------------------------------- /jest-integration/tests/multiple/4-update-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn run test-update 4 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/5-revalidate-snapshots/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 0, 3 | "numFailedTests": 0, 4 | "numPassedTestSuites": 2, 5 | "numPassedTests": 5, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 2, 10 | "numTotalTests": 5, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": false, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 6, 19 | "total": 6, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": true, 25 | "testResults": [ 26 | { 27 | "message": "", 28 | "name": "/src/__tests__/ClickCounter-other.spec.js", 29 | "summary": "", 30 | "status": "passed", 31 | "assertionResults": [ 32 | { 33 | "status": "passed", 34 | "title": "counts a single click" 35 | } 36 | ] 37 | }, 38 | { 39 | "message": "", 40 | "name": "/src/__tests__/ClickCounter.spec.js", 41 | "summary": "", 42 | "status": "passed", 43 | "assertionResults": [ 44 | { 45 | "status": "passed", 46 | "title": "counts a single click" 47 | }, 48 | { 49 | "status": "passed", 50 | "title": "counts multiple clicks" 51 | }, 52 | { 53 | "status": "passed", 54 | "title": "passes multiple snapshots in a single test" 55 | }, 56 | { 57 | "status": "passed", 58 | "title": "renders with default props" 59 | } 60 | ] 61 | } 62 | ], 63 | "wasInterrupted": false 64 | } 65 | -------------------------------------------------------------------------------- /jest-integration/tests/multiple/5-revalidate-snapshots/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/1-preact/Components.js: -------------------------------------------------------------------------------- 1 | import Preact, {h} from 'preact'; 2 | 3 | export class Message extends Preact.Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | this.onClick = this.onClick.bind(this); 9 | } 10 | 11 | onClick() { 12 | this.setState({ count: this.state.count + 1}); 13 | } 14 | 15 | render() { 16 | const { msg } = this.props; 17 | return {msg} {this.state.count}; 18 | } 19 | } 20 | 21 | export class Clicker extends Preact.Component { 22 | 23 | constructor() { 24 | super(); 25 | this.state = { count: 0 }; 26 | 27 | this.onClick = this.onClick.bind(this); 28 | this.onMouseDown = this.onMouseDown.bind(this); 29 | } 30 | 31 | onMouseDown(e) { 32 | this.setState({ count: this.state.count + e.clientX }) 33 | } 34 | 35 | onClick(e) { 36 | this.setState({ count: this.state.count + 1 }) 37 | } 38 | 39 | render() { 40 | return ( 41 |
42 | 43 |
44 | ) 45 | } 46 | } 47 | 48 | Clicker.displayName = 'ClickerDisplay'; 49 | 50 | export function Multiple() { 51 | return ( 52 |
53 | 54 | 55 |
56 | ); 57 | } 58 | 59 | export function Stateless({ msg }) { 60 | return {msg} 61 | } 62 | 63 | export function StatelessWrapper({ msg }) { 64 | return ; 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/1-preact/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 10, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 10, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 20, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": false, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 0, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [], 30 | "status": "passed", 31 | "title": "all pass" 32 | }, 33 | { 34 | "failureMessages": [ 35 | "UnexpectedError: \nexpected to render as\n
\n Clicked 0\n
\n\n
\n \n \n \n Clicked 0\n \n \n \n
\n" 36 | ], 37 | "status": "failed", 38 | "title": "class fail" 39 | }, 40 | { 41 | "failureMessages": [ 42 | "UnexpectedError: \nexpected \nto render as
Clicked 0
\n\n
\n \n \n \n Clicked 0\n \n \n \n
\n" 43 | ], 44 | "status": "failed", 45 | "title": "class fail with wrapper" 46 | }, 47 | { 48 | "failureMessages": [ 49 | "UnexpectedError: \nexpected \nto render as
foo
\n\n
\n \n \n \n Clicked 0 // -Clicked 0\n // +foo\n \n \n \n
\n" 50 | ], 51 | "status": "failed", 52 | "title": "content fail" 53 | }, 54 | { 55 | "failureMessages": [], 56 | "status": "passed", 57 | "title": "finds a custom component" 58 | }, 59 | { 60 | "failureMessages": [ 61 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on queried for to have rendered Clicked 3 not\n\n\n
\n \n \n \n Clicked 5 // -Clicked 5\n // +Clicked 3 not\n \n \n \n
\n
\n" 62 | ], 63 | "status": "failed", 64 | "title": "finds a custom component (fails)" 65 | }, 66 | { 67 | "failureMessages": [], 68 | "status": "passed", 69 | "title": "finds an HTML element with queryTarget " 70 | }, 71 | { 72 | "failureMessages": [ 73 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on queried for to have exactly rendered Clicked 4 not\n\n\n Clicked 5 // -Clicked 5\n // +Clicked 4 not\n\n" 74 | ], 75 | "status": "failed", 76 | "title": "finds an HTML element with queryTarget (fails)" 77 | }, 78 | { 79 | "failureMessages": [ 80 | "UnexpectedError: \nexpected \nto render as ff\n\n\n \n // missing ff\n \n\n" 81 | ], 82 | "status": "failed", 83 | "title": "renders a stateless component (fails)" 84 | }, 85 | { 86 | "failureMessages": [ 87 | "UnexpectedError: \nexpected { count: 5 } to satisfy { count: 4 }\n\n{\n count: 5 // should equal 4\n}\n\n at /Users/davebrotherstone/work/personal/unexpected-preact/jest-integration/build/src/__tests__/preact.spec.js:143:7\n" 88 | ], 89 | "status": "failed", 90 | "title": "returns a custom component from the promise (fails)" 91 | }, 92 | { 93 | "failureMessages": [ 94 | "UnexpectedError: \nexpected 'SPAN' to be 'SHOULD_BE_SPAN'\n\n-SPAN\n+SHOULD_BE_SPAN\n\n at /Users/davebrotherstone/work/personal/unexpected-preact/jest-integration/build/src/__tests__/preact.spec.js:153:7\n" 95 | ], 96 | "status": "failed", 97 | "title": "returns an HTML element from the promise (fails)" 98 | }, 99 | { 100 | "failureMessages": [], 101 | "status": "passed", 102 | "title": "simple pass" 103 | }, 104 | { 105 | "failureMessages": [], 106 | "status": "passed", 107 | "title": "simple pass with wrappers" 108 | }, 109 | { 110 | "failureMessages": [], 111 | "status": "passed", 112 | "title": "simple pass with wrappers and multiple events" 113 | }, 114 | { 115 | "failureMessages": [], 116 | "status": "passed", 117 | "title": "simple pass with wrappers and multiple events with args" 118 | }, 119 | { 120 | "failureMessages": [], 121 | "status": "passed", 122 | "title": "triggers an event on a queried for element" 123 | }, 124 | { 125 | "failureMessages": [], 126 | "status": "passed", 127 | "title": "uses the return from a stateless component render (pass)" 128 | }, 129 | { 130 | "failureMessages": [], 131 | "status": "passed", 132 | "title": "with `on` with attribute match triggers on right component" 133 | }, 134 | { 135 | "failureMessages": [ 136 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on to have rendered
Clicked 0Clicked 6
\n\n
\n \n
\n \n \n Clicked 0\n \n \n
\n
\n \n
\n \n \n \n Clicked 5 // -Clicked 5\n // +Clicked 6\n \n \n \n
\n
\n
\n" 137 | ], 138 | "status": "failed", 139 | "title": "with `on` with attribute match triggers on right component (fails)" 140 | }, 141 | { 142 | "failureMessages": [ 143 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } with event 'click', 'to have rendered', Clicked 2\n\n
\n \n \n \n Clicked 6 // -Clicked 6\n // +Clicked 2\n \n \n \n
\n" 144 | ], 145 | "status": "failed", 146 | "title": "with wrappers and multiple events with args (fails)" 147 | } 148 | ], 149 | "message": " ● to render as › content fail\n\n UnexpectedError: \n expected \n to render as
foo
\n \n
\n \n \n \n Clicked 0 // -Clicked 0\n // +foo\n \n \n \n
\n", 150 | "name": "/src/__tests__/preact.spec.js", 151 | "status": "failed", 152 | "summary": "" 153 | } 154 | ], 155 | "wasInterrupted": false 156 | } 157 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/1-preact/preact.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import unexpected from 'unexpected'; 3 | import unexpectedPreact from '../../unexpected-preact'; 4 | import { Multiple, Clicker, StatelessWrapper, Stateless } from '../Components'; 5 | 6 | const expect = unexpected.clone().use(unexpectedPreact); 7 | 8 | describe('to render as', function () { 9 | it('content fail', function () { 10 | expect(, 'to render as',
foo
) 11 | }); 12 | 13 | it('class fail', function () { 14 | expect(, 'to render as',
Clicked 0
) 15 | }); 16 | 17 | it('class fail with wrapper', function () { 18 | expect(, 'to render as',
Clicked 0
) 19 | }); 20 | 21 | it('all pass', function () { 22 | expect(, 'to render as',
Clicked 0
) 23 | }); 24 | 25 | }); 26 | 27 | describe('with event', function () { 28 | it('simple pass', function () { 29 | expect(, 30 | 'when rendered', 31 | 'with event','click', 32 | 'to have rendered', 33 |
Clicked 1
) 34 | }); 35 | 36 | it('simple pass with wrappers', function () { 37 | expect(, 38 | 'when rendered', 39 | 'with event','click', 40 | 'to have rendered', 41 | Clicked 1) 42 | }); 43 | 44 | it('simple pass with wrappers and multiple events', function () { 45 | expect(, 46 | 'when rendered', 47 | 'with event','click', 48 | 'with event','click', 49 | 'to have rendered', 50 | Clicked 2) 51 | }); 52 | 53 | it('simple pass with wrappers and multiple events with args', function () { 54 | expect(, 55 | 'when rendered', 56 | 'with event','mouseDown', { clientX: 5 }, 57 | 'with event','click', 58 | 'to have rendered', 59 | Clicked 6) 60 | }); 61 | 62 | it('with wrappers and multiple events with args (fails)', function () { 63 | expect(, 64 | 'when rendered', 65 | 'with event','mouseDown', { clientX: 5 }, 66 | 'with event','click', 67 | 'to have rendered', 68 | Clicked 2) 69 | }); 70 | 71 | it('with `on` with attribute match triggers on right component', function () { 72 | expect(, 73 | 'when rendered', 74 | 'with event','mouseDown', { clientX: 5 }, 'on', , 75 | 'to have rendered', 76 |
77 | Clicked 0 78 | Clicked 5 79 |
); 80 | }); 81 | 82 | it('with `on` with attribute match triggers on right component (fails)', function () { 83 | expect(, 84 | 'when rendered', 85 | 'with event','mouseDown', { clientX: 5 }, 'on', , 86 | 'to have rendered', 87 |
88 | Clicked 0 89 | Clicked 6 90 |
); 91 | }); 92 | 93 | 94 | }); 95 | 96 | describe('queried for', function () { 97 | it('finds a custom component', function () { 98 | expect(, 99 | 'when rendered', 100 | 'with event','mouseDown', { clientX: 5 }, 'on', , 101 | 'queried for', , 102 | 'to have rendered', 103 | Clicked 5 104 | ); 105 | }); 106 | 107 | it('finds a custom component (fails)', function () { 108 | expect(, 109 | 'when rendered', 110 | 'with event','mouseDown', { clientX: 5 }, 'on', , 111 | 'queried for', , 112 | 'to have rendered', 113 | Clicked 3 not 114 | ); 115 | }); 116 | 117 | it('finds an HTML element with queryTarget ', function () { 118 | expect(, 119 | 'when rendered', 120 | 'with event','mouseDown', { clientX: 5 }, 'on', , 121 | 'queried for', , 122 | 'to have exactly rendered', 123 | Clicked 5 124 | ); 125 | }); 126 | 127 | it('finds an HTML element with queryTarget (fails)', function () { 128 | expect(, 129 | 'when rendered', 130 | 'with event','mouseDown', { clientX: 5 }, 'on', , 131 | 'queried for', , 132 | 'to have exactly rendered', 133 | Clicked 4 not 134 | ); 135 | }); 136 | 137 | it('returns a custom component from the promise (fails)', function () { 138 | return expect(, 139 | 'when rendered', 140 | 'with event', 'mouseDown', { clientX: 5 }, 'on', , 141 | 'queried for', ) 142 | .then(comp => { 143 | expect(comp.state, 'to satisfy', { count: 4 }); 144 | }); 145 | }); 146 | 147 | it('returns an HTML element from the promise (fails)', function () { 148 | return expect(, 149 | 'when rendered', 150 | 'with event', 'mouseDown', { clientX: 5 }, 'on', , 151 | 'queried for', ) 152 | .then(comp => { 153 | expect(comp.tagName, 'to be', 'SHOULD_BE_SPAN'); 154 | }); 155 | }); 156 | 157 | it('triggers an event on a queried for element', function () { 158 | return expect(, 159 | 'when rendered', 160 | 'queried for', , 161 | 'with event', 'mouseDown', { clientX: 5 }, 162 | 'to have rendered', 163 | Clicked 5 164 | ); 165 | }); 166 | }); 167 | 168 | describe('Stateless', function () { 169 | it('renders a stateless component (fails)', function () { 170 | expect(, 'to render as', ff) 171 | }); 172 | 173 | it('uses the return from a stateless component render (pass)', function () { 174 | const container = document.createElement('div'); 175 | const instance = Preact.render(, container); 176 | expect(instance, 'to have rendered', cheese) 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/1-preact/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/preact.spec.js src/__tests__/ 4 | cp $1/Components.js src/ 5 | rm src/__tests__/ClickCounter.spec.js 6 | 7 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/1-preact/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/2-preact-compat/Components.js: -------------------------------------------------------------------------------- 1 | import Preact, { createElement } from 'preact-compat'; 2 | 3 | const h = createElement; 4 | 5 | export class Message extends Preact.Component { 6 | 7 | constructor() { 8 | super(); 9 | this.state = { count: 0 }; 10 | this.onClick = this.onClick.bind(this); 11 | } 12 | 13 | onClick() { 14 | this.setState({ count: this.state.count + 1}); 15 | } 16 | 17 | render() { 18 | const { msg } = this.props; 19 | return {msg} {this.state.count}; 20 | } 21 | } 22 | 23 | export class Clicker extends Preact.Component { 24 | 25 | constructor() { 26 | super(); 27 | this.state = { count: 0 }; 28 | 29 | this.onClick = this.onClick.bind(this); 30 | this.onMouseDown = this.onMouseDown.bind(this); 31 | } 32 | 33 | onMouseDown(e) { 34 | this.setState({ count: this.state.count + e.clientX }) 35 | } 36 | 37 | onClick(e) { 38 | this.setState({ count: this.state.count + 1 }) 39 | } 40 | 41 | render() { 42 | return ( 43 |
44 | 45 |
46 | ) 47 | } 48 | } 49 | 50 | Clicker.displayName = 'ClickerDisplay'; 51 | 52 | export function Multiple() { 53 | return ( 54 |
55 | 56 | 57 |
58 | ); 59 | } 60 | 61 | export function Stateless({ msg }) { 62 | return {msg} 63 | } 64 | 65 | export function StatelessWrapper({ msg }) { 66 | return ; 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/2-preact-compat/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 10, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 10, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 20, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": false, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 0, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [], 30 | "status": "passed", 31 | "title": "all pass" 32 | }, 33 | { 34 | "failureMessages": [ 35 | "UnexpectedError: \nexpected to render as\n
\n Clicked 0\n
\n\n
\n \n \n \n Clicked 0\n \n \n \n
\n" 36 | ], 37 | "status": "failed", 38 | "title": "class fail" 39 | }, 40 | { 41 | "failureMessages": [ 42 | "UnexpectedError: \nexpected \nto render as
Clicked 0
\n\n
\n \n \n \n Clicked 0\n \n \n \n
\n" 43 | ], 44 | "status": "failed", 45 | "title": "class fail with wrapper" 46 | }, 47 | { 48 | "failureMessages": [ 49 | "UnexpectedError: \nexpected \nto render as
foo
\n\n
\n \n \n \n Clicked 0 // -Clicked 0\n // +foo\n \n \n \n
\n" 50 | ], 51 | "status": "failed", 52 | "title": "content fail" 53 | }, 54 | { 55 | "failureMessages": [], 56 | "status": "passed", 57 | "title": "finds a custom component" 58 | }, 59 | { 60 | "failureMessages": [ 61 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on queried for to have rendered Clicked 3 not\n\n\n
\n \n \n \n Clicked 5 // -Clicked 5\n // +Clicked 3 not\n \n \n \n
\n
\n" 62 | ], 63 | "status": "failed", 64 | "title": "finds a custom component (fails)" 65 | }, 66 | { 67 | "failureMessages": [], 68 | "status": "passed", 69 | "title": "finds an HTML element with queryTarget " 70 | }, 71 | { 72 | "failureMessages": [ 73 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on queried for to have exactly rendered Clicked 4 not\n\n\n Clicked 5 // -Clicked 5\n // +Clicked 4 not\n\n" 74 | ], 75 | "status": "failed", 76 | "title": "finds an HTML element with queryTarget (fails)" 77 | }, 78 | { 79 | "failureMessages": [ 80 | "UnexpectedError: \nexpected \nto render as ff\n\n\n \n // missing ff\n \n\n" 81 | ], 82 | "status": "failed", 83 | "title": "renders a stateless component (fails)" 84 | }, 85 | { 86 | "failureMessages": [ 87 | "UnexpectedError: \nexpected { count: 5 } to satisfy { count: 4 }\n\n{\n count: 5 // should equal 4\n}\n\n at /Users/davebrotherstone/work/personal/unexpected-preact/jest-integration/build/src/__tests__/preact.spec.js:145:7\n" 88 | ], 89 | "status": "failed", 90 | "title": "returns a custom component from the promise (fails)" 91 | }, 92 | { 93 | "failureMessages": [ 94 | "UnexpectedError: \nexpected 'SPAN' to be 'SHOULD_BE_SPAN'\n\n-SPAN\n+SHOULD_BE_SPAN\n\n at /Users/davebrotherstone/work/personal/unexpected-preact/jest-integration/build/src/__tests__/preact.spec.js:155:7\n" 95 | ], 96 | "status": "failed", 97 | "title": "returns an HTML element from the promise (fails)" 98 | }, 99 | { 100 | "failureMessages": [], 101 | "status": "passed", 102 | "title": "simple pass" 103 | }, 104 | { 105 | "failureMessages": [], 106 | "status": "passed", 107 | "title": "simple pass with wrappers" 108 | }, 109 | { 110 | "failureMessages": [], 111 | "status": "passed", 112 | "title": "simple pass with wrappers and multiple events" 113 | }, 114 | { 115 | "failureMessages": [], 116 | "status": "passed", 117 | "title": "simple pass with wrappers and multiple events with args" 118 | }, 119 | { 120 | "failureMessages": [], 121 | "status": "passed", 122 | "title": "triggers an event on a queried for element" 123 | }, 124 | { 125 | "failureMessages": [], 126 | "status": "passed", 127 | "title": "uses the return from a stateless component render (pass)" 128 | }, 129 | { 130 | "failureMessages": [], 131 | "status": "passed", 132 | "title": "with `on` with attribute match triggers on right component" 133 | }, 134 | { 135 | "failureMessages": [ 136 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } on to have rendered
Clicked 0Clicked 6
\n\n
\n \n
\n \n \n Clicked 0\n \n \n
\n
\n \n
\n \n \n \n Clicked 5 // -Clicked 5\n // +Clicked 6\n \n \n \n
\n
\n
\n" 137 | ], 138 | "status": "failed", 139 | "title": "with `on` with attribute match triggers on right component (fails)" 140 | }, 141 | { 142 | "failureMessages": [ 143 | "UnexpectedError: \nexpected \nwhen rendered with event 'mouseDown', { clientX: 5 } with event 'click', 'to have rendered', Clicked 2\n\n
\n \n \n \n Clicked 6 // -Clicked 6\n // +Clicked 2\n \n \n \n
\n" 144 | ], 145 | "status": "failed", 146 | "title": "with wrappers and multiple events with args (fails)" 147 | } 148 | ], 149 | "message": " ● to render as › content fail\n\n UnexpectedError: \n expected \n to render as
foo
\n \n
\n \n \n \n Clicked 0 // -Clicked 0\n // +foo\n \n \n \n
\n", 150 | "name": "/src/__tests__/preact.spec.js", 151 | "status": "failed", 152 | "summary": "" 153 | } 154 | ], 155 | "wasInterrupted": false 156 | } 157 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/2-preact-compat/preact.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { createElement } from 'preact-compat'; 2 | import unexpected from 'unexpected'; 3 | import unexpectedPreact from '../../unexpected-preact'; 4 | import { Multiple, Clicker, StatelessWrapper, Stateless } from '../Components'; 5 | 6 | const expect = unexpected.clone().use(unexpectedPreact); 7 | 8 | const h = createElement; 9 | 10 | describe('to render as', function () { 11 | it('content fail', function () { 12 | expect(, 'to render as',
foo
) 13 | }); 14 | 15 | it('class fail', function () { 16 | expect(, 'to render as',
Clicked 0
) 17 | }); 18 | 19 | it('class fail with wrapper', function () { 20 | expect(, 'to render as',
Clicked 0
) 21 | }); 22 | 23 | it('all pass', function () { 24 | expect(, 'to render as',
Clicked 0
) 25 | }); 26 | 27 | }); 28 | 29 | describe('with event', function () { 30 | it('simple pass', function () { 31 | expect(, 32 | 'when rendered', 33 | 'with event','click', 34 | 'to have rendered', 35 |
Clicked 1
) 36 | }); 37 | 38 | it('simple pass with wrappers', function () { 39 | expect(, 40 | 'when rendered', 41 | 'with event','click', 42 | 'to have rendered', 43 | Clicked 1) 44 | }); 45 | 46 | it('simple pass with wrappers and multiple events', function () { 47 | expect(, 48 | 'when rendered', 49 | 'with event','click', 50 | 'with event','click', 51 | 'to have rendered', 52 | Clicked 2) 53 | }); 54 | 55 | it('simple pass with wrappers and multiple events with args', function () { 56 | expect(, 57 | 'when rendered', 58 | 'with event','mouseDown', { clientX: 5 }, 59 | 'with event','click', 60 | 'to have rendered', 61 | Clicked 6) 62 | }); 63 | 64 | it('with wrappers and multiple events with args (fails)', function () { 65 | expect(, 66 | 'when rendered', 67 | 'with event','mouseDown', { clientX: 5 }, 68 | 'with event','click', 69 | 'to have rendered', 70 | Clicked 2) 71 | }); 72 | 73 | it('with `on` with attribute match triggers on right component', function () { 74 | expect(, 75 | 'when rendered', 76 | 'with event','mouseDown', { clientX: 5 }, 'on', , 77 | 'to have rendered', 78 |
79 | Clicked 0 80 | Clicked 5 81 |
); 82 | }); 83 | 84 | it('with `on` with attribute match triggers on right component (fails)', function () { 85 | expect(, 86 | 'when rendered', 87 | 'with event','mouseDown', { clientX: 5 }, 'on', , 88 | 'to have rendered', 89 |
90 | Clicked 0 91 | Clicked 6 92 |
); 93 | }); 94 | 95 | 96 | }); 97 | 98 | describe('queried for', function () { 99 | it('finds a custom component', function () { 100 | expect(, 101 | 'when rendered', 102 | 'with event','mouseDown', { clientX: 5 }, 'on', , 103 | 'queried for', , 104 | 'to have rendered', 105 | Clicked 5 106 | ); 107 | }); 108 | 109 | it('finds a custom component (fails)', function () { 110 | expect(, 111 | 'when rendered', 112 | 'with event','mouseDown', { clientX: 5 }, 'on', , 113 | 'queried for', , 114 | 'to have rendered', 115 | Clicked 3 not 116 | ); 117 | }); 118 | 119 | it('finds an HTML element with queryTarget ', function () { 120 | expect(, 121 | 'when rendered', 122 | 'with event','mouseDown', { clientX: 5 }, 'on', , 123 | 'queried for', , 124 | 'to have exactly rendered', 125 | Clicked 5 126 | ); 127 | }); 128 | 129 | it('finds an HTML element with queryTarget (fails)', function () { 130 | expect(, 131 | 'when rendered', 132 | 'with event','mouseDown', { clientX: 5 }, 'on', , 133 | 'queried for', , 134 | 'to have exactly rendered', 135 | Clicked 4 not 136 | ); 137 | }); 138 | 139 | it('returns a custom component from the promise (fails)', function () { 140 | return expect(, 141 | 'when rendered', 142 | 'with event', 'mouseDown', { clientX: 5 }, 'on', , 143 | 'queried for', ) 144 | .then(comp => { 145 | expect(comp.state, 'to satisfy', { count: 4 }); 146 | }); 147 | }); 148 | 149 | it('returns an HTML element from the promise (fails)', function () { 150 | return expect(, 151 | 'when rendered', 152 | 'with event', 'mouseDown', { clientX: 5 }, 'on', , 153 | 'queried for', ) 154 | .then(comp => { 155 | expect(comp.tagName, 'to be', 'SHOULD_BE_SPAN'); 156 | }); 157 | }); 158 | 159 | it('triggers an event on a queried for element', function () { 160 | return expect(, 161 | 'when rendered', 162 | 'queried for', , 163 | 'with event', 'mouseDown', { clientX: 5 }, 164 | 'to have rendered', 165 | Clicked 5 166 | ); 167 | }); 168 | }); 169 | 170 | describe('Stateless', function () { 171 | it('renders a stateless component (fails)', function () { 172 | expect(, 'to render as', ff) 173 | }); 174 | 175 | it('uses the return from a stateless component render (pass)', function () { 176 | const container = document.createElement('div'); 177 | const instance = Preact.render(, container); 178 | expect(instance, 'to have rendered', cheese) 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/2-preact-compat/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/preact.spec.js src/__tests__/ 4 | cp $1/Components.js src/ 5 | 6 | -------------------------------------------------------------------------------- /jest-integration/tests/preact-general/2-preact-compat/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/tests/wrong-require/1-failures/ClickCounter.spec.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | import ClickCounter from '../ClickCounter'; 3 | import Unexpected from 'unexpected'; 4 | 5 | import UnexpectedPreact from '../../unexpected-preact-non-jest'; 6 | 7 | const expect = Unexpected.clone().use(UnexpectedPreact); 8 | 9 | describe('ClickCounter', function () { 10 | 11 | it('renders with default props', function () { 12 | // This will fail as we've not included the unexpected-react/jest 13 | const container = document.createElement('div'); 14 | const instance = Preact.render(, container); 15 | expect(instance, 'to match snapshot'); 16 | }); 17 | 18 | it('renders with `when rendered`', function () { 19 | // This will also fail, but should include the `when rendered` message 20 | expect(, 'when rendered', 'to match snapshot'); 21 | }); 22 | 23 | it('renders with default props with to satisfy', function () { 24 | const container = document.createElement('div'); 25 | const instance = Preact.render(, container); 26 | // This will fail as we've not included the unexpected-react/jest 27 | expect(instance, 'to satisfy snapshot'); 28 | }); 29 | 30 | it('renders with `when rendered to satisfy`', function () { 31 | // This will also fail, but should include the `when rendered` message 32 | expect(, 'when rendered', 'to satisfy snapshot'); 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /jest-integration/tests/wrong-require/1-failures/expectedOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "numFailedTestSuites": 1, 3 | "numFailedTests": 4, 4 | "numPassedTestSuites": 0, 5 | "numPassedTests": 0, 6 | "numPendingTestSuites": 0, 7 | "numPendingTests": 0, 8 | "numRuntimeErrorTestSuites": 0, 9 | "numTotalTestSuites": 1, 10 | "numTotalTests": 4, 11 | "snapshot": { 12 | "added": 0, 13 | "failure": false, 14 | "filesAdded": 0, 15 | "filesRemoved": 0, 16 | "filesUnmatched": 0, 17 | "filesUpdated": 0, 18 | "matched": 0, 19 | "total": 0, 20 | "unchecked": 0, 21 | "unmatched": 0, 22 | "updated": 0 23 | }, 24 | "success": false, 25 | "testResults": [ 26 | { 27 | "assertionResults": [ 28 | { 29 | "failureMessages": [ 30 | "UnexpectedError: \nexpected when rendered to satisfy snapshot\n\nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n" 31 | ], 32 | "status": "failed", 33 | "title": "renders with `when rendered to satisfy`" 34 | }, 35 | { 36 | "failureMessages": [ 37 | "UnexpectedError: \nexpected when rendered to match snapshot\n\nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n" 38 | ], 39 | "status": "failed", 40 | "title": "renders with `when rendered`" 41 | }, 42 | { 43 | "failureMessages": [ 44 | "UnexpectedError: \nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n\nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n" 45 | ], 46 | "status": "failed", 47 | "title": "renders with default props" 48 | }, 49 | { 50 | "failureMessages": [ 51 | "UnexpectedError: \nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n\nTo use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n" 52 | ], 53 | "status": "failed", 54 | "title": "renders with default props with to satisfy" 55 | } 56 | ], 57 | "message": " ● ClickCounter › renders with default props\n\n UnexpectedError: \n To use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n \n To use snapshot assertions in jest, use 'unexpected-preact/jest' to require or import unexpected-preact\n", 58 | "name": "/src/__tests__/ClickCounter.spec.js", 59 | "status": "failed", 60 | "summary": "" 61 | } 62 | ], 63 | "wasInterrupted": false 64 | } 65 | -------------------------------------------------------------------------------- /jest-integration/tests/wrong-require/1-failures/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp $1/ClickCounter.spec.js src/__tests__/ 4 | 5 | -------------------------------------------------------------------------------- /jest-integration/tests/wrong-require/1-failures/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn test 4 | -------------------------------------------------------------------------------- /jest-integration/update-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -ne 2 ]; then 4 | echo "Usage: $0 tests// " 5 | return; 6 | fi 7 | 8 | if [ -d $1 ]; then 9 | STEPNAME=$(basename $1) 10 | TESTDIR=$(dirname $1) 11 | TESTNAME=$(basename $TESTDIR) 12 | 13 | if [ "$2" = "stderr" ]; then 14 | cp "output/$TESTNAME/$STEPNAME/stderr_clean" "tests/$TESTNAME/$STEPNAME/expected_stderr" 15 | elif [ "$2" = "json" ]; then 16 | cp "output/$TESTNAME/$STEPNAME/testRunOutput_clean.json" "tests/$TESTNAME/$STEPNAME/expectedOutput.json" 17 | elif [ "$2" = "stdout" ]; then 18 | cp "output/$TESTNAME/$STEPNAME/stdout_clean" "tests/$TESTNAME/$STEPNAME/expected_stdout" 19 | else 20 | echo "Must specify to update 'stdout', 'stderr' or 'json' as the second parameter" 21 | exit 1 22 | fi 23 | 24 | echo Updated $TESTNAME $STEPNAME; 25 | else 26 | echo Test $TESTNAME step $STEPNAME was not found 27 | fi 28 | -------------------------------------------------------------------------------- /jest.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/jest'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unexpected-preact", 3 | "version": "2.0.4", 4 | "description": "Assertion library for preact.js - sister project to unexpected-react", 5 | "main": "lib/unexpected-preact.js", 6 | "repository": "https://github.com/bruderstein/unexpected-preact.git", 7 | "scripts": { 8 | "test": "mocha --compilers js:babel-register src/**/*.spec.js", 9 | "integration-test": "cd jest-integration && ./run.sh", 10 | "build": "babel src -d lib --source-maps", 11 | "prepublish": "npm run build", 12 | "generate-site": "generate-site --require ./bootstrap-documentation-generation.js", 13 | "deploy-site": "deploy-site.sh" 14 | }, 15 | "keywords": [ 16 | "preact", 17 | "preact.js", 18 | "testing", 19 | "unit test", 20 | "assertion" 21 | ], 22 | "author": "Dave Brotherstone", 23 | "license": "MIT", 24 | "dependencies": { 25 | "js-writer": "^1.2.0", 26 | "magicpen-prism": "^2.3.0", 27 | "unexpected-htmllike": "^2.1.2", 28 | "unexpected-htmllike-preact-adapter": "^1.2.2", 29 | "unexpected-htmllike-preactrendered-adapter": "^1.3.0", 30 | "unexpected-htmllike-raw-adapter": "^1.0.1" 31 | }, 32 | "devDependencies": { 33 | "babel-cli": "^6.24.1", 34 | "babel-core": "^6.9.1", 35 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 36 | "babel-preset-es2015": "^6.9.0", 37 | "babel-preset-react": "^6.5.0", 38 | "babel-register": "^6.24.0", 39 | "bluebird": "^3.5.0", 40 | "jest-matchers": "^19.0.0", 41 | "jsdom": "^9.12.0", 42 | "mocha": "^3.2.0", 43 | "mock-fs": "^4.2.0", 44 | "preact": "^8.1.0", 45 | "preact-compat": "^3.15.0", 46 | "sinon": "^2.1.0", 47 | "unexpected": "^10.26.3", 48 | "unexpected-documentation-site-generator": "^4.4.0", 49 | "unexpected-markdown": "^1.7.2", 50 | "unexpected-sinon": "^10.7.1" 51 | }, 52 | "peerDependencies": { 53 | "preact": ">= 7.0.0 <= 9.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/assertions/AssertionGenerator.js: -------------------------------------------------------------------------------- 1 | import UnexpectedHtmlLike from 'unexpected-htmllike'; 2 | 3 | const PENDING_TEST_EVENT_TYPE = { dummy: 'Dummy object to identify a pending event on the test renderer' }; 4 | 5 | function getDefaultOptions(flags) { 6 | return { 7 | diffWrappers: flags.exactly || flags.withAllWrappers, 8 | diffExtraChildren: flags.exactly || flags.withAllChildren, 9 | diffExtraAttributes: flags.exactly || flags.withAllAttributes, 10 | diffExactClasses: flags.exactly, 11 | diffExtraClasses: flags.exactly || flags.withAllClasses 12 | }; 13 | } 14 | 15 | /** 16 | * 17 | * @param options {object} 18 | * @param options.ActualAdapter {function} constructor function for the HtmlLike adapter for the `actual` value (usually the renderer) 19 | * @param options.ExpectedAdapter {function} constructor function for the HtmlLike adapter for the `expected` value 20 | * @param options.QueryAdapter {function} constructor function for the HtmlLike adapter for the query value (`queried for` and `on`) 21 | * @param options.actualTypeName {string} name of the unexpected type for the `actual` value 22 | * @param options.expectedTypeName {string} name of the unexpected type for the `expected` value 23 | * @param options.queryTypeName {string} name of the unexpected type for the query value (used in `queried for` and `on`) 24 | * @param options.actualRenderOutputType {string} the unexpected type for the actual output value 25 | * @param options.getRenderOutput {function} called with the actual value, and returns the `actualRenderOutputType` type 26 | * @param options.getDiffInputFromRenderOutput {function} called with the value from `getRenderOutput`, result passed to HtmlLike diff 27 | * @param options.rewrapResult {function} called with the `actual` value (usually the renderer), and the found result 28 | * @param options.wrapResultForReturn {function} called with the `actual` value (usually the renderer), and the found result 29 | * from HtmlLike `contains()` call (usually the same type returned from `getDiffInputFromRenderOutput`. Used to create a 30 | * value that can be passed back to the user as the result of the promise. Used by `queried for` when no further assertion is 31 | * provided, therefore the return value is provided as the result of the promise. If this is not present, `rewrapResult` is used. 32 | * @param options.triggerEvent {function} called the `actual` value (renderer), the optional target (or null) as the result 33 | * from the HtmlLike `contains()` call target, the eventName, and optional eventArgs when provided (undefined otherwise) 34 | * @constructor 35 | */ 36 | function AssertionGenerator(options) { 37 | this._options = Object.assign({}, options); 38 | this._PENDING_EVENT_IDENTIFIER = (options.mainAssertionGenerator && options.mainAssertionGenerator.getEventIdentifier()) || 39 | { dummy: options.actualTypeName + 'PendingEventIdentifier' }; 40 | this._actualPendingEventTypeName = options.actualTypeName + 'PendingEvent'; 41 | } 42 | 43 | AssertionGenerator.prototype.getEventIdentifier = function () { 44 | return this._PENDING_EVENT_IDENTIFIER; 45 | }; 46 | 47 | AssertionGenerator.prototype.installInto = function installInto(expect) { 48 | this._installEqualityAssertions(expect); 49 | this._installQueriedFor(expect); 50 | this._installPendingEventType(expect); 51 | this._installWithEvent(expect); 52 | this._installWithEventOn(expect); 53 | this._installEventHandlerAssertions(expect); 54 | }; 55 | 56 | AssertionGenerator.prototype.installAlternativeExpected = function (expect) { 57 | this._installEqualityAssertions(expect); 58 | this._installEventHandlerAssertions(expect); 59 | }; 60 | 61 | AssertionGenerator.prototype._installEqualityAssertions = function (expect) { 62 | const { 63 | actualTypeName, expectedTypeName, 64 | getRenderOutput, actualRenderOutputType, 65 | getDiffInputFromRenderOutput, 66 | ActualAdapter, ExpectedAdapter 67 | } = this._options; 68 | 69 | 70 | expect.addAssertion([`<${actualTypeName}> to have [exactly] rendered <${expectedTypeName}>`, 71 | `<${actualTypeName}> to have rendered [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}>`], 72 | function (expect, subject, renderOutput) { 73 | var actual = getRenderOutput(subject); 74 | return expect(actual, 'to have [exactly] rendered [with all children] [with all wrappers] [with all classes] [with all attributes]', renderOutput); 75 | }); 76 | 77 | expect.addAssertion([ 78 | `<${actualRenderOutputType}> to have [exactly] rendered <${expectedTypeName}>`, 79 | `<${actualRenderOutputType}> to have rendered [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}>` 80 | ], function (expect, subject, renderOutput) { 81 | 82 | const exactly = this.flags.exactly; 83 | const withAllChildren = this.flags['with all children']; 84 | const withAllWrappers = this.flags['with all wrappers']; 85 | const withAllClasses = this.flags['with all classes']; 86 | const withAllAttributes = this.flags['with all attributes']; 87 | 88 | const actualAdapter = new ActualAdapter({ includeKeyProp: true }); 89 | const expectedAdapter = new ExpectedAdapter({ includeKeyProp: true }); 90 | const testHtmlLike = new UnexpectedHtmlLike(actualAdapter); 91 | if (!exactly) { 92 | expectedAdapter.setOptions({concatTextContent: true}); 93 | actualAdapter.setOptions({concatTextContent: true}); 94 | } 95 | 96 | const options = getDefaultOptions({exactly, withAllWrappers, withAllChildren, withAllClasses, withAllAttributes}); 97 | 98 | const diffResult = testHtmlLike.diff(expectedAdapter, getDiffInputFromRenderOutput(subject), renderOutput, expect, options); 99 | 100 | return testHtmlLike.withResult(diffResult, result => { 101 | 102 | if (result.weight !== 0) { 103 | return expect.fail({ 104 | diff: function (output, diff, inspect) { 105 | return { 106 | diff: output.append(testHtmlLike.render(result, output.clone(), diff, inspect)) 107 | }; 108 | } 109 | }); 110 | } 111 | }); 112 | }); 113 | 114 | expect.addAssertion([`<${actualTypeName}> [not] to contain [exactly] <${expectedTypeName}|string>`, 115 | `<${actualTypeName}> [not] to contain [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}|string>`], function (expect, subject, renderOutput) { 116 | var actual = getRenderOutput(subject); 117 | return expect(actual, '[not] to contain [exactly] [with all children] [with all wrappers] [with all classes] [with all attributes]', renderOutput); 118 | }); 119 | 120 | expect.addAssertion([`<${actualRenderOutputType}> [not] to contain [exactly] <${expectedTypeName}|string>`, 121 | `<${actualRenderOutputType}> [not] to contain [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}|string>`], function (expect, subject, expected) { 122 | 123 | var not = this.flags.not; 124 | var exactly = this.flags.exactly; 125 | var withAllChildren = this.flags['with all children']; 126 | var withAllWrappers = this.flags['with all wrappers']; 127 | var withAllClasses = this.flags['with all classes']; 128 | var withAllAttributes = this.flags['with all attributes']; 129 | 130 | var actualAdapter = new ActualAdapter({ includeKeyProp: true }); 131 | var expectedAdapter = new ExpectedAdapter({ includeKeyProp: true }); 132 | var testHtmlLike = new UnexpectedHtmlLike(actualAdapter); 133 | if (!exactly) { 134 | actualAdapter.setOptions({concatTextContent: true}); 135 | expectedAdapter.setOptions({concatTextContent: true}); 136 | } 137 | 138 | var options = getDefaultOptions({exactly, withAllWrappers, withAllChildren, withAllClasses, withAllAttributes}); 139 | 140 | const containsResult = testHtmlLike.contains(expectedAdapter, getDiffInputFromRenderOutput(subject), expected, expect, options); 141 | 142 | return testHtmlLike.withResult(containsResult, result => { 143 | 144 | if (not) { 145 | if (result.found) { 146 | expect.fail({ 147 | diff: (output, diff, inspect) => { 148 | return { 149 | diff: output.error('but found the following match').nl().append(testHtmlLike.render(result.bestMatch, output.clone(), diff, inspect)) 150 | }; 151 | } 152 | }); 153 | } 154 | return; 155 | } 156 | 157 | if (!result.found) { 158 | expect.fail({ 159 | diff: function (output, diff, inspect) { 160 | return { 161 | diff: output.error('the best match was').nl().append(testHtmlLike.render(result.bestMatch, output.clone(), diff, inspect)) 162 | }; 163 | } 164 | }); 165 | } 166 | }); 167 | }); 168 | 169 | // More generic assertions 170 | expect.addAssertion(`<${actualTypeName}> to equal <${expectedTypeName}>`, function (expect, subject, expected) { 171 | expect(getRenderOutput(subject), 'to equal', expected); 172 | }); 173 | expect.addAssertion(`<${actualRenderOutputType}> to equal <${expectedTypeName}>`, function (expect, subject, expected) { 174 | expect(subject, 'to have exactly rendered', expected); 175 | }); 176 | 177 | expect.addAssertion(`<${actualTypeName}> to satisfy <${expectedTypeName}>`, function (expect, subject, expected) { 178 | expect(getRenderOutput(subject), 'to satisfy', expected); 179 | }); 180 | 181 | expect.addAssertion(`<${actualRenderOutputType}> to satisfy <${expectedTypeName}>`, function (expect, subject, expected) { 182 | expect(subject, 'to have rendered', expected); 183 | }); 184 | }; 185 | 186 | AssertionGenerator.prototype._installQueriedFor = function (expect) { 187 | 188 | const { 189 | actualTypeName, queryTypeName, 190 | getRenderOutput, actualRenderOutputType, 191 | getDiffInputFromRenderOutput, rewrapResult, wrapResultForReturn, 192 | ActualAdapter, QueryAdapter 193 | } = this._options; 194 | 195 | expect.addAssertion([`<${actualTypeName}> queried for [exactly] <${queryTypeName}> `, 196 | `<${actualTypeName}> queried for [with all children] [with all wrapppers] [with all classes] [with all attributes] <${queryTypeName}> ` 197 | ], function (expect, subject, query, assertion) { 198 | return expect.apply(expect, 199 | [ 200 | getRenderOutput(subject), 'queried for [exactly] [with all children] [with all wrappers] [with all classes] [with all attributes]', query 201 | ].concat(Array.prototype.slice.call(arguments, 3))); 202 | }); 203 | 204 | expect.addAssertion([`<${actualRenderOutputType}> queried for [exactly] <${queryTypeName}> `, 205 | `<${actualRenderOutputType}> queried for [with all children] [with all wrapppers] [with all classes] [with all attributes] <${queryTypeName}> `], function (expect, subject, query) { 206 | 207 | var exactly = this.flags.exactly; 208 | var withAllChildren = this.flags['with all children']; 209 | var withAllWrappers = this.flags['with all wrappers']; 210 | var withAllClasses = this.flags['with all classes']; 211 | var withAllAttributes = this.flags['with all attributes']; 212 | 213 | var actualAdapter = new ActualAdapter({ includeKeyProp: true }); 214 | var queryAdapter = new QueryAdapter({ includeKeyProp: true }); 215 | var testHtmlLike = new UnexpectedHtmlLike(actualAdapter); 216 | if (!exactly) { 217 | actualAdapter.setOptions({concatTextContent: true}); 218 | queryAdapter.setOptions({concatTextContent: true}); 219 | } 220 | 221 | const options = getDefaultOptions({exactly, withAllWrappers, withAllChildren, withAllClasses, withAllAttributes}); 222 | options.findTargetAttrib = 'queryTarget'; 223 | 224 | const containsResult = testHtmlLike.contains(queryAdapter, getDiffInputFromRenderOutput(subject), query, expect, options); 225 | 226 | const args = arguments; 227 | 228 | return testHtmlLike.withResult(containsResult, function (result) { 229 | 230 | if (!result.found) { 231 | expect.fail({ 232 | diff: (output, diff, inspect) => { 233 | const resultOutput = { 234 | diff: output.error('`queried for` found no match.') 235 | }; 236 | if (result.bestMatch) { 237 | resultOutput.diff.error(' The best match was') 238 | .nl() 239 | .append(testHtmlLike.render(result.bestMatch, output.clone(), diff, inspect)); 240 | } 241 | return resultOutput; 242 | } 243 | }); 244 | } 245 | 246 | if (args.length > 3) { 247 | // There is an assertion continuation... 248 | expect.errorMode = 'nested'; 249 | const s = rewrapResult(subject, result.bestMatch.target || result.bestMatchItem); 250 | return expect.apply(null, 251 | [ 252 | rewrapResult(subject, result.bestMatch.target || result.bestMatchItem) 253 | ].concat(Array.prototype.slice.call(args, 3))) 254 | return expect.shift(rewrapResult(subject, result.bestMatch.target || result.bestMatchItem)); 255 | } 256 | // There is no assertion continuation, so we need to wrap the result for public consumption 257 | // i.e. create a value that we can give back from the `expect` promise 258 | return expect.shift((wrapResultForReturn || rewrapResult)(subject, result.bestMatch.target || result.bestMatchItem)); 259 | }); 260 | }); 261 | 262 | }; 263 | 264 | AssertionGenerator.prototype._installPendingEventType = function (expect) { 265 | 266 | const actualPendingEventTypeName = this._actualPendingEventTypeName; 267 | 268 | const PENDING_EVENT_IDENTIFIER = this._PENDING_EVENT_IDENTIFIER; 269 | 270 | expect.addType({ 271 | name: actualPendingEventTypeName, 272 | base: 'object', 273 | identify(value) { 274 | return value && typeof value === 'object' && value.$$typeof === PENDING_EVENT_IDENTIFIER; 275 | }, 276 | inspect(value, depth, output, inspect) { 277 | return output.append(inspect(value.renderer)).red(' with pending event \'').cyan(value.eventName).red('\''); 278 | } 279 | }); 280 | }; 281 | 282 | AssertionGenerator.prototype._installWithEvent = function (expect) { 283 | 284 | const { actualTypeName, actualRenderOutputType, triggerEvent, canTriggerEventsOnOutputType } = this._options; 285 | let { wrapResultForReturn = (value) => value } = this._options; 286 | 287 | const actualPendingEventTypeName = this._actualPendingEventTypeName; 288 | 289 | const PENDING_EVENT_IDENTIFIER = this._PENDING_EVENT_IDENTIFIER; 290 | 291 | expect.addAssertion(`<${actualTypeName}> with event `, function (expect, subject, eventName, ...assertion) { 292 | if (arguments.length > 3) { 293 | return expect.apply(null, [{ 294 | $$typeof: PENDING_EVENT_IDENTIFIER, 295 | renderer: subject, 296 | eventName: eventName 297 | }].concat(assertion)); 298 | } else { 299 | triggerEvent(subject, null, eventName); 300 | return expect.shift(wrapResultForReturn(subject)); 301 | } 302 | }); 303 | 304 | 305 | expect.addAssertion(`<${actualTypeName}> with event `, function (expect, subject, eventName, args) { 306 | if (arguments.length > 4) { 307 | return expect.shift({ 308 | $$typeof: PENDING_EVENT_IDENTIFIER, 309 | renderer: subject, 310 | eventName: eventName, 311 | eventArgs: args 312 | }); 313 | } else { 314 | triggerEvent(subject, null, eventName, args); 315 | return expect.shift(wrapResultForReturn(subject)); 316 | } 317 | }); 318 | 319 | if (canTriggerEventsOnOutputType) { 320 | 321 | expect.addAssertion(`<${actualRenderOutputType}> with event `, function (expect, subject, eventName, ...assertion) { 322 | if (arguments.length > 3) { 323 | expect.errorMode = 'bubble'; 324 | return expect.apply(null, [{ 325 | $$typeof: PENDING_EVENT_IDENTIFIER, 326 | renderer: subject, 327 | eventName: eventName, 328 | isOutputType: true 329 | }].concat(assertion)); 330 | } else { 331 | triggerEvent(subject, null, eventName); 332 | return expect.shift(wrapResultForReturn(subject)); 333 | } 334 | }); 335 | 336 | expect.addAssertion(`<${actualRenderOutputType}> with event `, function (expect, subject, eventName, args) { 337 | if (arguments.length > 4) { 338 | return expect.shift({ 339 | $$typeof: PENDING_EVENT_IDENTIFIER, 340 | renderer: subject, 341 | eventName: eventName, 342 | eventArgs: args, 343 | isOutputType: true 344 | }); 345 | } else { 346 | triggerEvent(subject, null, eventName, args); 347 | return expect.shift(subject); 348 | } 349 | }); 350 | 351 | } 352 | 353 | expect.addAssertion(`<${actualPendingEventTypeName}> [and] with event `, 354 | function (expect, subject, eventName) { 355 | triggerEvent(subject.renderer, subject.target, subject.eventName, subject.eventArgs); 356 | if (arguments.length > 3) { 357 | return expect.shift({ 358 | $$typeof: PENDING_EVENT_IDENTIFIER, 359 | renderer: subject.renderer, 360 | eventName: eventName 361 | }); 362 | } else { 363 | triggerEvent(subject.renderer, null, eventName); 364 | return expect.shift(wrapResultForReturn(subject.renderer)); 365 | } 366 | }); 367 | 368 | expect.addAssertion(`<${actualPendingEventTypeName}> [and] with event `, 369 | function (expect, subject, eventName, eventArgs) { 370 | 371 | triggerEvent(subject.renderer, subject.target, subject.eventName, subject.eventArgs); 372 | if (arguments.length > 4) { 373 | return expect.shift({ 374 | $$typeof: PENDING_EVENT_IDENTIFIER, 375 | renderer: subject.renderer, 376 | eventName: eventName, 377 | eventArgs: eventArgs 378 | }); 379 | } else { 380 | triggerEvent(subject.renderer, null, eventName, eventArgs); 381 | return expect.shift(wrapResultForReturn(subject.renderer)); 382 | } 383 | }); 384 | 385 | }; 386 | 387 | AssertionGenerator.prototype._installWithEventOn = function (expect) { 388 | 389 | const { 390 | actualTypeName, queryTypeName, expectedTypeName, 391 | getRenderOutput, 392 | getDiffInputFromRenderOutput, triggerEvent, 393 | wrapResultForReturn, 394 | ActualAdapter, QueryAdapter 395 | } = this._options; 396 | 397 | const actualPendingEventTypeName = this._actualPendingEventTypeName; 398 | 399 | expect.addAssertion(`<${actualPendingEventTypeName}> on [exactly] [with all children] [with all wrappers] [with all classes] [with all attributes]<${queryTypeName}> `, 400 | function (expect, subject, target) { 401 | const actualAdapter = new ActualAdapter({ includeKeyProp: true, convertToString: true, concatTextContent: true }); 402 | const queryAdapter = new QueryAdapter({ includeKeyProp: true, convertToString: true, concatTextContent: true }); 403 | const testHtmlLike = new UnexpectedHtmlLike(actualAdapter); 404 | 405 | const exactly = this.flags.exactly; 406 | const withAllChildren = this.flags['with all children']; 407 | const withAllWrappers = this.flags['with all wrappers']; 408 | const withAllClasses = this.flags['with all classes']; 409 | const withAllAttributes = this.flags['with all attributes']; 410 | 411 | const options = getDefaultOptions({ exactly, withAllWrappers, withAllChildren, withAllClasses, withAllAttributes}); 412 | options.findTargetAttrib = 'eventTarget'; 413 | const containsResult = testHtmlLike.contains(queryAdapter, getDiffInputFromRenderOutput(getRenderOutput(subject.renderer)), target, expect, options); 414 | return testHtmlLike.withResult(containsResult, result => { 415 | if (!result.found) { 416 | return expect.fail({ 417 | diff: function (output, diff, inspect) { 418 | output.error('Could not find the target for the event. '); 419 | if (result.bestMatch) { 420 | output.error('The best match was').nl().nl().append(testHtmlLike.render(result.bestMatch, output.clone(), diff, inspect)); 421 | } 422 | return output; 423 | } 424 | }); 425 | } 426 | 427 | const newSubject = Object.assign({}, subject, { 428 | target: result.bestMatch.target || result.bestMatchItem 429 | }); 430 | 431 | if (arguments.length > 3) { 432 | return expect.shift(newSubject); 433 | } else { 434 | triggerEvent(newSubject.renderer, newSubject.target, newSubject.eventName, newSubject.eventArgs); 435 | return expect.shift(wrapResultForReturn(newSubject.renderer)); 436 | } 437 | }); 438 | }); 439 | 440 | expect.addAssertion([`<${actualPendingEventTypeName}> queried for [exactly] <${queryTypeName}> `, 441 | `<${actualPendingEventTypeName}> queried for [with all children] [with all wrappers] [with all classes] [with all attributes] <${queryTypeName}> `], 442 | function (expect, subject, expected) { 443 | 444 | triggerEvent(subject.renderer, subject.target, subject.eventName, subject.eventArgs); 445 | return expect.apply(expect, 446 | [subject.renderer, 'queried for [exactly] [with all children] [with all wrappers] [with all classes] [with all attributes]', expected] 447 | .concat(Array.prototype.slice.call(arguments, 3))); 448 | } 449 | ); 450 | }; 451 | 452 | AssertionGenerator.prototype._installEventHandlerAssertions = function (expect) { 453 | const { actualTypeName, expectedTypeName, triggerEvent } = this._options; 454 | 455 | const actualPendingEventTypeName = this._actualPendingEventTypeName; 456 | 457 | expect.addAssertion([`<${actualPendingEventTypeName}> [not] to contain [exactly] <${expectedTypeName}|string>`, 458 | `<${actualPendingEventTypeName}> [not] to contain [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}|string>`], 459 | function (expect, subject, expected) { 460 | triggerEvent(subject.renderer, subject.target, subject.eventName, subject.eventArgs); 461 | return expect(subject.renderer, '[not] to contain [exactly] [with all children] [with all wrappers] [with all classes] [with all attributes]', expected); 462 | }); 463 | 464 | expect.addAssertion(`<${actualPendingEventTypeName}> to have [exactly] rendered [with all children] [with all wrappers] [with all classes] [with all attributes] <${expectedTypeName}|string>`, 465 | function (expect, subject, expected) { 466 | triggerEvent(subject.renderer, subject.target, subject.eventName, subject.eventArgs); 467 | return expect(subject.renderer, 'to have [exactly] rendered [with all children] [with all wrappers] [with all classes] [with all attributes]', expected); 468 | }); 469 | }; 470 | 471 | 472 | export default AssertionGenerator; 473 | -------------------------------------------------------------------------------- /src/assertions/deepAgainstRawAssertions.js: -------------------------------------------------------------------------------- 1 | import UnexpectedHtmlLike from 'unexpected-htmllike'; 2 | import PreactRenderedAdapter from 'unexpected-htmllike-preactrendered-adapter'; 3 | import PreactElementAdapter from 'unexpected-htmllike-preact-adapter'; 4 | import RawAdapter from 'unexpected-htmllike-raw-adapter'; 5 | import Preact from 'preact'; 6 | import AssertionGenerator from './AssertionGenerator'; 7 | import { triggerEvent } from './deepAssertions'; 8 | 9 | function getOptions(expect) { 10 | // Override the classAttributeName as we're always comparing against `class` here 11 | RawAdapter.prototype.classAttributeName = 'class'; 12 | 13 | return { 14 | ActualAdapter: PreactRenderedAdapter, 15 | QueryAdapter: PreactElementAdapter, 16 | ExpectedAdapter: RawAdapter, 17 | actualTypeName: 'RenderedPreactElement', 18 | queryTypeName: 'PreactElement', 19 | expectedTypeName: 'ReactRawObjectElement', 20 | getRenderOutput: component => { 21 | if (component && 22 | typeof component === 'object' && 23 | (component.type === PreactRenderedAdapter.COMPONENT_TYPE || 24 | component.type === PreactRenderedAdapter.NODE_TYPE)) { 25 | return component; 26 | } 27 | return PreactRenderedAdapter.wrapRootNode(component); 28 | }, 29 | actualRenderOutputType: 'RenderedPreactElementWrapper', 30 | getDiffInputFromRenderOutput: renderOutput => renderOutput, 31 | rewrapResult: (renderer, target) => target, 32 | wrapResultForReturn: (component, target) => { 33 | const result = (target || component); 34 | if (!result) { 35 | return result; 36 | } 37 | if (result.type === PreactRenderedAdapter.COMPONENT_TYPE) { 38 | return result.component; 39 | } 40 | if (result.type === PreactRenderedAdapter.NODE_TYPE) { 41 | return result.node; 42 | } 43 | return result; 44 | }, 45 | triggerEvent: triggerEvent.bind(null, expect), 46 | canTriggerEventsOnOutputType: true 47 | }; 48 | } 49 | 50 | function installInto(expect) { 51 | const assertionGenerator = new AssertionGenerator(getOptions(expect)); 52 | assertionGenerator.installInto(expect); 53 | 54 | return assertionGenerator; 55 | } 56 | 57 | function installAsAlternative(expect, mainAssertionGenerator) { 58 | const generatorOptions = getOptions(expect); 59 | const assertionGenerator = new AssertionGenerator({ mainAssertionGenerator, ...generatorOptions }); 60 | assertionGenerator.installAlternativeExpected(expect); 61 | } 62 | 63 | export { installInto, installAsAlternative, triggerEvent }; 64 | -------------------------------------------------------------------------------- /src/assertions/deepAssertions.js: -------------------------------------------------------------------------------- 1 | import UnexpectedHtmlLike from 'unexpected-htmllike'; 2 | import PreactRenderedAdapter from 'unexpected-htmllike-preactrendered-adapter'; 3 | import PreactElementAdapter from 'unexpected-htmllike-preact-adapter'; 4 | import AssertionGenerator from './AssertionGenerator'; 5 | import { h, Component, render } from 'preact'; 6 | 7 | 8 | // These are events that have a more specific constructor 9 | const eventConstructorMap = { 10 | animationend: window.AnimationEvent, 11 | animationiteration: window.AnimationEvent, 12 | animationstart: window.AnimationEvent, 13 | beforeunload: window.BeforeUnloadEvent, 14 | beginEvent: window.TimeEvent, 15 | blur: window.FocusEvent, 16 | click: window.MouseEvent, 17 | compositionend: window.CompositionEvent, 18 | compositionstart: window.CompositionEvent, 19 | compositionupdate: window.CompositionEvent, 20 | contextmenu: window.MouseEvent, 21 | dblclick: window.MouseEvent, 22 | DOMActivate: window.UIEvent, 23 | DOMAttributeNameChanged: window.MutationNameEvent, 24 | DOMAttrModified: window.MutationEvent, 25 | DOMCharacterDataModified: window.MutationEvent, 26 | DOMElementNameChanged: window.MutationNameEvent, 27 | DOMNodeInserted: window.MutationEvent, 28 | DOMNodeInsertedIntoDocument: window.MutationEvent, 29 | DOMNodeRemoved: window.MutationEvent, 30 | DOMNodeRemovedFromDocument: window.MutationEvent, 31 | DOMSubtreeModified: window.MutationEvent, 32 | drag: window.DragEvent, 33 | dragend: window.DragEvent, 34 | dragenter: window.DragEvent, 35 | dragleave: window.DragEvent, 36 | dragover: window.DragEvent, 37 | dragstart: window.DragEvent, 38 | drop: window.DragEvent, 39 | end: window.SpeechSynthesisEvent, 40 | error: window.UIEvent, 41 | focus: window.FocusEvent, 42 | gamepadconnected: window.GamepadEvent, 43 | gamepaddisconnected: window.GamepadEvent, 44 | gotpointercapture: window.PointerEvent, 45 | hashchange: window.HashChangeEvent, 46 | lostpointercapture: window.PointerEvent, 47 | keydown: window.KeyboardEvent, 48 | keypress: window.KeyboardEvent, 49 | keyup: window.KeyboardEvent, 50 | load: window.UIEvent, 51 | mousedown: window.MouseEvent, 52 | mouseenter: window.MouseEvent, 53 | mouseleave: window.MouseEvent, 54 | mousemove: window.MouseEvent, 55 | mouseout: window.MouseEvent, 56 | mouseover: window.MouseEvent, 57 | mouseup: window.MouseEvent, 58 | paste: window.ClipboardEvent, 59 | pointercancel: window.PointerEvent, 60 | pointerdown: window.PointerEvent, 61 | pointerenter: window.PointerEvent, 62 | pointerleave: window.PointerEvent, 63 | pointermove: window.PointerEvent, 64 | pointerout: window.PointerEvent, 65 | pointerover: window.PointerEvent, 66 | pointerup: window.PointerEvent, 67 | repeatEvent: window.TimeEvent, 68 | resize: window.UIEvent, 69 | scroll: window.UIEvent, 70 | select: window.UIEvent, 71 | show: window.MouseEvent, 72 | SVGAbort: window.SVGEvent, 73 | SVGError: window.SVGEvent, 74 | SVGLoad: window.SVGEvent, 75 | SVGResize: window.SVGEvent, 76 | SVGScroll: window.SVGEvent, 77 | SVGUnload: window.SVGEvent, 78 | SVGZoom: window.SVGZoomEvent, 79 | touchcancel: window.TouchEvent, 80 | touchend: window.TouchEvent, 81 | touchmove: window.TouchEvent, 82 | touchstart: window.TouchEvent, 83 | transitionend: window.TransitionEvent, 84 | unload: window.UIEvent, 85 | wheel: window.WheelEvent 86 | }; 87 | 88 | // Map the lowercase to the canonical case of the event 89 | const caseMapping = { 90 | animationend: 'animationend', 91 | animationiteration: 'animationiteration', 92 | animationstart: 'animationstart', 93 | beforeunload: 'beforeunload', 94 | beginevent: 'beginEvent', 95 | blur: 'blur', 96 | click: 'click', 97 | compositionend: 'compositionend', 98 | compositionstart: 'compositionstart', 99 | compositionupdate: 'compositionupdate', 100 | contextmenu: 'contextmenu', 101 | dblclick: 'dblclick', 102 | domactivate: 'DOMActivate', 103 | domattributenamechanged: 'DOMAttributeNameChanged', 104 | domattrmodified: 'DOMAttrModified', 105 | domcharacterdatamodified: 'DOMCharacterDataModified', 106 | domelementnamechanged: 'DOMElementNameChanged', 107 | domnodeinserted: 'DOMNodeInserted', 108 | domnodeinsertedintodocument: 'DOMNodeInsertedIntoDocument', 109 | domnoderemoved: 'DOMNodeRemoved', 110 | domnoderemovedfromdocument: 'DOMNodeRemovedFromDocument', 111 | domsubtreemodified: 'DOMSubtreeModified', 112 | drag: 'drag', 113 | dragend: 'dragend', 114 | dragenter: 'dragenter', 115 | dragleave: 'dragleave', 116 | dragover: 'dragover', 117 | dragstart: 'dragstart', 118 | drop: 'drop', 119 | end: 'end', 120 | error: 'error', 121 | focus: 'focus', 122 | gamepadconnected: 'gamepadconnected', 123 | gamepaddisconnected: 'gamepaddisconnected', 124 | gotpointercapture: 'gotpointercapture', 125 | hashchange: 'hashchange', 126 | lostpointercapture: 'lostpointercapture', 127 | keydown: 'keydown', 128 | keypress: 'keypress', 129 | keyup: 'keyup', 130 | load: 'load', 131 | mousedown: 'mousedown', 132 | mouseenter: 'mouseenter', 133 | mouseleave: 'mouseleave', 134 | mousemove: 'mousemove', 135 | mouseout: 'mouseout', 136 | mouseover: 'mouseover', 137 | mouseup: 'mouseup', 138 | paste: 'paste', 139 | pointercancel: 'pointercancel', 140 | pointerdown: 'pointerdown', 141 | pointerenter: 'pointerenter', 142 | pointerleave: 'pointerleave', 143 | pointermove: 'pointermove', 144 | pointerout: 'pointerout', 145 | pointerover: 'pointerover', 146 | pointerup: 'pointerup', 147 | repeatevent: 'repeatEvent', 148 | resize: 'resize', 149 | scroll: 'scroll', 150 | select: 'select', 151 | show: 'show', 152 | svgabort: 'SVGAbort', 153 | svgerror: 'SVGError', 154 | svgload: 'SVGLoad', 155 | svgresize: 'SVGResize', 156 | svgscroll: 'SVGScroll', 157 | svgunload: 'SVGUnload', 158 | svgzoom: 'SVGZoom', 159 | touchcancel: 'touchcancel', 160 | touchend: 'touchend', 161 | touchmove: 'touchmove', 162 | touchstart: 'touchstart', 163 | transitionend: 'transitionend', 164 | unload: 'unload', 165 | wheel: 'wheel' 166 | }; 167 | 168 | function triggerEvent(expect, component, target, eventName, eventArgs) { 169 | let targetDOM = component; 170 | 171 | if (!target && component && ( 172 | component.type === PreactRenderedAdapter.COMPONENT_TYPE || 173 | component && component.type === PreactRenderedAdapter.NODE_TYPE 174 | )) { 175 | targetDOM = component.node; 176 | } 177 | 178 | if (targetDOM.hasOwnProperty('props') && targetDOM.hasOwnProperty('context') && typeof targetDOM.setState === 'function') { 179 | // This is an instance of a component from preact-compat 180 | targetDOM = targetDOM.base; 181 | } 182 | if (target) { 183 | targetDOM = target.node; 184 | } 185 | 186 | // Map the case 187 | eventName = caseMapping[eventName.toLowerCase()] || eventName; 188 | const EventConstructor = eventConstructorMap[eventName] || window.Event; 189 | let event = new EventConstructor( 190 | eventName, 191 | Object.assign({ bubbles: true, cancellable: true, view: window }, eventArgs) 192 | ); 193 | targetDOM.dispatchEvent(event); 194 | } 195 | 196 | function renderIntoDocument(element) { 197 | const container = window.document.createElement('div'); 198 | return render(element, container); 199 | } 200 | 201 | function installInto(expect) { 202 | 203 | const assertionGenerator = new AssertionGenerator({ 204 | ActualAdapter: PreactRenderedAdapter, 205 | QueryAdapter: PreactElementAdapter, 206 | ExpectedAdapter: PreactElementAdapter, 207 | actualTypeName: 'RenderedPreactElement', 208 | queryTypeName: 'PreactElement', 209 | expectedTypeName: 'PreactElement', 210 | getRenderOutput: component => { 211 | if (component && 212 | typeof component === 'object' && 213 | (component.type === PreactRenderedAdapter.COMPONENT_TYPE || 214 | component.type === PreactRenderedAdapter.NODE_TYPE)) { 215 | return component; 216 | } 217 | return PreactRenderedAdapter.wrapRootNode(component); 218 | }, 219 | actualRenderOutputType: 'RenderedPreactElementWrapper', 220 | getDiffInputFromRenderOutput: renderOutput => renderOutput, 221 | rewrapResult: (renderer, target) => target, 222 | wrapResultForReturn: (component, target) => { 223 | const result = (target || component); 224 | if (!result) { 225 | return result; 226 | } 227 | if (result.type === PreactRenderedAdapter.COMPONENT_TYPE) { 228 | return result.component; 229 | } 230 | if (result.type === PreactRenderedAdapter.NODE_TYPE) { 231 | return result.node; 232 | } 233 | if (result._component) { 234 | return result._component; 235 | } 236 | return result; 237 | }, 238 | triggerEvent: triggerEvent.bind(null, expect), 239 | canTriggerEventsOnOutputType: true 240 | }); 241 | 242 | assertionGenerator.installInto(expect); 243 | 244 | class StatelessWrapper extends Component { 245 | render() { 246 | return this.props.children; 247 | } 248 | } 249 | 250 | expect.addAssertion(' when [deeply] rendered ', function (expect, subject) { 251 | let component; 252 | component = renderIntoDocument(subject); 253 | return expect.shift(component); 254 | }); 255 | 256 | expect.addAssertion(' to [exactly] [deeply] render [with all children] [with all wrappers] [with all classes] [with all attributes] as ', function (expect, subject, expected) { 257 | 258 | if (this.flags.exactly) { 259 | return expect(subject, 'when deeply rendered', 'to have exactly rendered', expected); 260 | } 261 | return expect(subject, 'when deeply rendered to have rendered [with all children] [with all wrappers] [with all classes] [with all attributes]', expected); 262 | }); 263 | } 264 | 265 | export { installInto, triggerEvent }; 266 | -------------------------------------------------------------------------------- /src/assertions/jestSnapshotStandardRendererAssertions.js: -------------------------------------------------------------------------------- 1 | import RawAdapter from 'unexpected-htmllike-raw-adapter'; 2 | import PreactElementAdapter from 'unexpected-htmllike-preact-adapter'; 3 | import PreactRenderedAdapter from 'unexpected-htmllike-preactrendered-adapter'; 4 | import { triggerEvent } from './deepAssertions' 5 | import { compareSnapshot } from '../helpers/snapshots'; 6 | 7 | 8 | function installInto(expect) { 9 | 10 | const rawAdapter = new RawAdapter({ convertToString: true, concatTextContent: true }); 11 | const preactAdapter = new PreactElementAdapter({ convertToString: true }); 12 | const renderedPreactAdapter = new PreactRenderedAdapter({ convertToString: true, concatTextContent: true }); 13 | 14 | expect.addAssertion(' to match snapshot', 15 | function (expect, subject) { 16 | compareSnapshot(expect, this.flags, renderedPreactAdapter, subject, PreactRenderedAdapter.wrapRootNode(subject)); 17 | } 18 | ); 19 | 20 | expect.addAssertion(' to match snapshot', 21 | function (expect, subject) { 22 | triggerEvent(expect, subject.renderer, subject.target, subject.eventName, subject.eventArgs); 23 | expect(subject.renderer, 'to match snapshot'); 24 | } 25 | ); 26 | 27 | expect.addAssertion(' to satisfy snapshot', 28 | function (expect, subject) { 29 | compareSnapshot(expect, { satisfy: true }, renderedPreactAdapter, subject, PreactRenderedAdapter.wrapRootNode(subject)); 30 | } 31 | ); 32 | 33 | expect.addAssertion(' to satisfy snapshot', 34 | function (expect, subject) { 35 | triggerEvent(expect, subject.renderer, subject.target, subject.eventName, subject.eventArgs); 36 | expect(subject.renderer, 'to satisfy snapshot'); 37 | } 38 | ); 39 | } 40 | 41 | module.exports = { installInto }; 42 | -------------------------------------------------------------------------------- /src/assertions/snapshotFunctionAssertions.js: -------------------------------------------------------------------------------- 1 | import { getFunctionArgs } from '../helpers/snapshots'; 2 | 3 | function installInto(expect) { 4 | 5 | expect.addAssertion(' to satisfy ', function (expect, subject, value) { 6 | expect(functionToString(subject), 'to equal', snapshotFunctionToString(value)); 7 | }); 8 | 9 | expect.addAssertion(' to equal ', function (expect, subject, value) { 10 | expect(functionToString(subject), 'to equal', snapshotFunctionToString(value)); 11 | }); 12 | 13 | function functionToString(func) { 14 | return `function ${func.name}(${getFunctionArgs(func)}) { /* function body */ }`; 15 | } 16 | 17 | function snapshotFunctionToString(func) { 18 | return `function ${func.name}(${func.args}) { /* function body */ }` 19 | } 20 | 21 | } 22 | 23 | module.exports = { installInto }; -------------------------------------------------------------------------------- /src/helpers/snapshotLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fs from 'fs'; 4 | 5 | function defaultLoader(snapshotPath) { 6 | if (fs.statSync(snapshotPath).isFile()) { 7 | delete require.cache[snapshotPath]; 8 | return require(snapshotPath); 9 | } 10 | return null; 11 | } 12 | 13 | let loader = defaultLoader; 14 | 15 | function loadSnapshot(snapshotPath) { 16 | let content; 17 | try { 18 | if (fs.statSync(snapshotPath).isFile()) { 19 | content = loader(snapshotPath); 20 | } 21 | } catch (e) { 22 | content = null; 23 | } 24 | return content; 25 | } 26 | 27 | function injectLoader(newLoader) { 28 | loader = newLoader; 29 | } 30 | 31 | export { loadSnapshot, injectLoader }; 32 | -------------------------------------------------------------------------------- /src/helpers/snapshots.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import matchers from 'jest-matchers'; 3 | import jsWriter from 'js-writer'; 4 | import mkdirp from 'mkdirp'; 5 | import path from 'path'; 6 | import RawAdapter from 'unexpected-htmllike-raw-adapter'; 7 | import { loadSnapshot } from './snapshotLoader'; 8 | 9 | 10 | // Serializable "unique" ID 11 | const FUNCTION_ID = "$FUNC$bc*(!CDKRRz195123$"; 12 | const FUNC_ARGS_REGEX = /function [^(]*\(([^)]*)\)/; 13 | 14 | function getFunctionArgs(func) { 15 | const match = FUNC_ARGS_REGEX.exec(func.toString()); 16 | if (match) { 17 | return match[1].split(',').map(arg => arg.trim()).join(', '); 18 | } 19 | return ''; 20 | } 21 | 22 | const writerOptions = { 23 | handlers: { 24 | 'function': function (func) { 25 | const functionDefinition = { 26 | $functype: FUNCTION_ID, 27 | name: func.name || '', 28 | args: getFunctionArgs(func) 29 | }; 30 | return JSON.stringify(functionDefinition) 31 | } 32 | } 33 | }; 34 | 35 | class UnexpectedSnapshotState { 36 | 37 | constructor(snapshotState) { 38 | const files = {}; 39 | this._files = files; 40 | 41 | } 42 | 43 | getSnapshot(testPath, testName, expect) { 44 | let snapshot = this._files[testPath]; 45 | if (!snapshot) { 46 | const snapshotPath = getSnapshotPath(testPath); 47 | let content = loadSnapshot(snapshotPath); 48 | let contentOutput = {}; 49 | if (content) { 50 | Object.keys(content).reduce((agg, testKey) => { 51 | agg[testKey] = expect.output.clone().annotationBlock(function () { 52 | this.append(expect.inspect(rawAdapter.deserialize(content[testKey]))); 53 | }).toString(); 54 | return agg; 55 | }, contentOutput) 56 | } 57 | 58 | snapshot = this._files[testPath] = { 59 | testCounter: {}, 60 | uncheckedKeys: (content && new Set(Object.keys(content))) || new Set(), 61 | allTests: content || {}, 62 | contentOutput: contentOutput, 63 | failedTests: new Set() 64 | } 65 | } 66 | const count = (snapshot.testCounter[testName] || 0) + 1; 67 | snapshot.testCounter[testName] = count; 68 | 69 | const keyName = testName + ' ' + count; 70 | snapshot.uncheckedKeys.delete(keyName); 71 | return snapshot.allTests[keyName] || null; 72 | } 73 | 74 | saveSnapshot(testPath, testName, tree, expect) { 75 | 76 | const snapshotPath = getSnapshotPath(testPath); 77 | const snapshot = this._files[testPath]; 78 | 79 | // If we've been passed a new tree, update the current snapshot 80 | // Otherwise, we're just saving the file 81 | if (tree) { 82 | const count = snapshot.testCounter[testName] || 1; 83 | 84 | snapshot.allTests[testName + ' ' + count] = tree; 85 | snapshot.contentOutput[testName + ' ' + count] = expect.output.clone().annotationBlock(function () { 86 | this.append(expect.inspect(tree)); 87 | }).toString(); 88 | } 89 | const dir = path.dirname(snapshotPath); 90 | let exists; 91 | try { 92 | const statResult = fs.statSync(dir) 93 | console.log('statResult', dir, 'is', statResult.isDirectory()); 94 | exists = statResult.isDirectory(); 95 | 96 | } catch (e) { 97 | console.log('Exception doing fs.statSync', e); 98 | exists = false; 99 | } 100 | if (!exists) { 101 | mkdirp.sync(dir); 102 | } 103 | const fileContent = Object.keys(snapshot.allTests).map(test => { 104 | const display = snapshot.contentOutput[test] || '// Display unavailable (this is probably a bug in unexpected-react, please report it!)'; 105 | return `/////////////////// ${test} ///////////////////\n\n${display}\n\nexports[\`${test}\`] = ${jsWriter(snapshot.allTests[test], writerOptions)};\n// ===========================================================================\n`; 106 | }).join('\n\n'); 107 | fs.writeFileSync(snapshotPath, fileContent); 108 | } 109 | 110 | markTestAsFailed(testPath, testName) { 111 | const snapshot = this._files[testPath]; 112 | snapshot.failedTests.add(testName); 113 | } 114 | } 115 | 116 | function getSnapshotPath(testPath) { 117 | const testPathParsed = path.parse(testPath); 118 | testPathParsed.dir = path.join(testPathParsed.dir, '__snapshots__'); 119 | testPathParsed.base = testPathParsed.name + '.unexpected-snap'; 120 | 121 | return path.format(testPathParsed); 122 | } 123 | 124 | const rawAdapter = new RawAdapter({ convertToString: true, concatTextContent: true }); 125 | 126 | function compareSnapshot(expect, flags, subjectAdapter, subjectRenderer, subjectOutput) { 127 | 128 | const state = matchers.getState(); 129 | 130 | if (!state.unexpectedSnapshot) { 131 | state.unexpectedSnapshot = new UnexpectedSnapshotState(state.snapshotState); 132 | } 133 | 134 | const snapshot = state.unexpectedSnapshot.getSnapshot(state.testPath, state.currentTestName, expect); 135 | if (snapshot === null) { 136 | // Write and save 137 | state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName, rawAdapter.serialize(subjectAdapter, subjectOutput), expect); 138 | state.snapshotState.added++; 139 | } else { 140 | expect.withError(() => { 141 | if (flags.satisfy) { 142 | expect(subjectRenderer, 'to have rendered', rawAdapter.deserialize(snapshot)); 143 | } else { 144 | expect(subjectRenderer, 'to have rendered with all children with all wrappers with all classes with all attributes', rawAdapter.deserialize(snapshot)); 145 | } 146 | state.snapshotState.matched = (state.snapshotState.matched || 0) + 1; 147 | }, function (err) { 148 | state.unexpectedSnapshot.markTestAsFailed(state.testPath, state.currentTestName); 149 | if (state.snapshotState.update === true) { 150 | state.snapshotState.updated++; 151 | state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName, rawAdapter.serialize(subjectAdapter, subjectOutput), expect); 152 | } else { 153 | state.snapshotState.unmatched++; 154 | expect.fail(err); 155 | } 156 | }); 157 | } 158 | } 159 | 160 | function injectStateHooks() { 161 | const state = matchers.getState(); 162 | const snapshotState = state && state.snapshotState; 163 | if (snapshotState) { 164 | const originalRemoveUncheckedKeys = snapshotState.removeUncheckedKeys; 165 | 166 | snapshotState.removeUncheckedKeys = function () { 167 | const state = matchers.getState(); 168 | let isDirty = false; 169 | const snapshot = state.unexpectedSnapshot && state.unexpectedSnapshot._files[state.testPath]; 170 | if (snapshot && snapshot.uncheckedKeys.size) { 171 | snapshot.uncheckedKeys.forEach(key => { 172 | const testName = /(.*)\s[0-9]+$/.exec(key)[1]; 173 | 174 | if (!snapshot.failedTests.has(testName)) { 175 | isDirty = true; 176 | delete snapshot.allTests[key] 177 | } 178 | }); 179 | } 180 | 181 | if (!snapshot || Object.keys(snapshot.allTests).length === 0) { 182 | const snapshotPath = getSnapshotPath(state.testPath); 183 | try { 184 | if (fs.statSync(snapshotPath).isFile()) { 185 | fs.unlinkSync(getSnapshotPath(state.testPath)); 186 | } 187 | } catch (e) { 188 | // We're ignoring file-not-found exceptions, and errors deleting 189 | } 190 | 191 | if (state.unexpectedSnapshot) { 192 | delete state.unexpectedSnapshot._files[state.testPath]; 193 | } 194 | } else if (isDirty) { 195 | state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName); 196 | } 197 | originalRemoveUncheckedKeys.call(snapshotState); 198 | }; 199 | } 200 | } 201 | 202 | // When this module is required, Jest is already started, and the hooks can be added 203 | injectStateHooks(); 204 | 205 | export { 206 | compareSnapshot, 207 | injectStateHooks, // This is exported for testing purposes, and is not normally needed 208 | FUNCTION_ID, 209 | writerOptions, 210 | getFunctionArgs 211 | } 212 | -------------------------------------------------------------------------------- /src/jest.js: -------------------------------------------------------------------------------- 1 | import types from './types/types'; 2 | import * as deepAssertions from './assertions/deepAssertions'; 3 | import * as deepAgainstRawAssertions from './assertions/deepAgainstRawAssertions'; 4 | import jestSnapshotStandardRendererAssertions from './assertions/jestSnapshotStandardRendererAssertions'; 5 | import snapshotFunctionType from './types/snapshotFunctionType'; 6 | import snapshotFunctionAssertions from './assertions/snapshotFunctionAssertions'; 7 | 8 | 9 | const Preact = require('preact'); 10 | Preact.options.debounceRendering = function (fn) { return fn(); }; 11 | 12 | 13 | 14 | module.exports = { 15 | name: 'unexpected-preact', 16 | 17 | installInto(expect) { 18 | 19 | expect.installPlugin(require('magicpen-prism')); 20 | 21 | types.installInto(expect); 22 | const mainAssertionGenerator = deepAssertions.installInto(expect); 23 | deepAgainstRawAssertions.installAsAlternative(expect, mainAssertionGenerator); 24 | jestSnapshotStandardRendererAssertions.installInto(expect); 25 | snapshotFunctionType.installInto(expect); 26 | snapshotFunctionAssertions.installInto(expect); 27 | }, 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /src/tests/components/ClickCounter.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | 3 | export default class ClickCounter extends Preact.Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | this.onClick = this.onClick.bind(this); 9 | } 10 | 11 | onClick() { 12 | this.setState({ 13 | count: this.state.count + 1 14 | }); 15 | } 16 | 17 | render() { 18 | 19 | // This is built like this so the prop is only defined if it's provided, so we don't even pass an undefined prop 20 | const extraProps = {}; 21 | if (this.props.ariaLabel) { 22 | extraProps.ariaLabel = this.props.ariaLabel; 23 | } 24 | 25 | return ( 26 | 29 | ); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/tests/components/Clicker.js: -------------------------------------------------------------------------------- 1 | import Preact, { h } from 'preact'; 2 | 3 | export class Clicker extends Preact.Component { 4 | 5 | constructor() { 6 | super(); 7 | this.state = { count: 0 }; 8 | 9 | this.onClick = this.onClick.bind(this); 10 | this.onMouseDown = this.onMouseDown.bind(this); 11 | } 12 | 13 | onMouseDown(e) { 14 | this.setState({ count: this.state.count + e.clientX }) 15 | } 16 | 17 | onClick(e) { 18 | this.setState({ count: this.state.count + 1 }) 19 | } 20 | 21 | render() { 22 | return ( 23 |
24 | 25 |
26 | ) 27 | } 28 | } 29 | 30 | Clicker.displayName = 'ClickerDisplay'; 31 | 32 | export function Multiple() { 33 | return ( 34 |
35 | 36 | 37 |
38 | ); 39 | } 40 | 41 | export function Stateless({ msg }) { 42 | return {msg} 43 | } 44 | 45 | export function StatelessWrapper({ msg }) { 46 | return ; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/tests/fixtures/functions.js: -------------------------------------------------------------------------------- 1 | 2 | function bound1() { /* stuff */ } 3 | function bound2() { return 42; } 4 | function bound3(a, b) { return a + b; } 5 | 6 | var someObject = {}; 7 | 8 | // These are all helper functions that return a new instance of a function of the given type 9 | // They are in this separate file such that when running with wallaby, they don't get instrumented (which makes them all different!) 10 | module.exports = { 11 | anonymous: () => function () { /* stuff */ }, 12 | anonymousContent: () => function () { return 42; }, 13 | anonymousContentArgs: () => function (a, b) { return a + b; }, 14 | named: () => function doStuff() { /* stuff */ }, 15 | namedContent: () => function doStuff() { return 42; }, 16 | namedContentArgs: () => function doStuff(a, b) { 17 | // comment 18 | return a + b; 19 | }, 20 | bound: () => bound1.bind(someObject), 21 | boundContent: () => bound2.bind(someObject), 22 | boundContentArgs: () => bound3.bind(someObject) 23 | }; -------------------------------------------------------------------------------- /src/tests/fixtures/multiple.snapshot: -------------------------------------------------------------------------------- 1 | exports['multi test one 1'] = { type: 'button', props: { onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 0, ' times' ] }; 2 | exports['multi test two 1'] = { type: 'button', props: { onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 1, ' times' ] }; 3 | exports['multi test two 2'] = { type: 'button', props: { onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 2, ' times' ] }; 4 | -------------------------------------------------------------------------------- /src/tests/fixtures/multipleclasses.snapshot: -------------------------------------------------------------------------------- 1 | exports['multiple classes 1'] = { 2 | type: 'button', props: { class: 'one two three', onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 0, ' times' ] 3 | } 4 | 5 | exports['multiple classes click 1'] = { 6 | type: 'button', props: { class: 'one two three', onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 1, ' times' ] 7 | } 8 | -------------------------------------------------------------------------------- /src/tests/fixtures/single.snapshot: -------------------------------------------------------------------------------- 1 | 2 | exports['single test name 1'] = { type: 'button', props: { onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 0, ' times' ] }; 3 | exports['single test name 2'] = { type: 'button', props: { onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 1, ' times' ] }; 4 | -------------------------------------------------------------------------------- /src/tests/fixtures/twoclicks.snapshot: -------------------------------------------------------------------------------- 1 | exports['two counters 1'] = { 2 | type: 'TwoClickCounters', 3 | props: {}, 4 | children: [ 5 | { type: 'div', props: {}, children: [ 6 | { type: 'ClickCounter', props: { className: 'one' }, children: 7 | [ 8 | { type: 'button', props: { className: 'one', onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 0, ' times' ] } 9 | ] 10 | }, 11 | { type: 'ClickCounter', props: { className: 'two' }, children: 12 | [ 13 | { type: 'button', props: { className: 'two', onClick: { $functype: '$FUNC$bc*(!CDKRRz195123$', name: 'bound onClick', args: '' } }, children: [ 'Clicked ', 0, ' times' ] } 14 | ] 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/tests/helpers/emulateDom.js: -------------------------------------------------------------------------------- 1 | if (typeof document === 'undefined') { 2 | 3 | const jsdom = require('jsdom').jsdom; 4 | global.document = jsdom(''); 5 | global.window = global.document.defaultView; 6 | 7 | for (let key in global.window) { 8 | if (!global[key]) { 9 | global[key] = global.window[key]; 10 | } 11 | } 12 | global.Node = global.window.Node; 13 | global.Text = global.window.Text; 14 | } 15 | -------------------------------------------------------------------------------- /src/tests/helpers/mock-jasmine.js: -------------------------------------------------------------------------------- 1 | global.jasmine = { 2 | matchersUtil: {} 3 | }; -------------------------------------------------------------------------------- /src/tests/snapshot.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import './helpers/emulateDom' 3 | import ClickCounter from './components/ClickCounter'; 4 | import fs from 'fs'; 5 | import mockFs from 'mock-fs'; 6 | import Module from 'module'; 7 | 8 | import path from 'path'; 9 | import Promise from 'bluebird'; 10 | 11 | import Preact, {h} from 'preact'; 12 | import Sinon from 'sinon'; 13 | import { injectLoader } from '../helpers/snapshotLoader'; 14 | import Unexpected from 'unexpected'; 15 | import UnexpectedSinon from 'unexpected-sinon'; 16 | // Note: These are imported later than the others, so that jasmine is mocked for the jest-matchers, but 17 | // unexpected does not think it's running under jasmine 18 | import mockJasmine from './helpers/mock-jasmine'; 19 | import JestMatchers from 'jest-matchers'; 20 | import UnexpectedPreact from '../jest' 21 | import functionFixtures from './fixtures/functions'; 22 | import { injectStateHooks } from '../helpers/snapshots'; 23 | 24 | function loadSnapshotMock(snapshotPath) { 25 | const snapModule = new Module(snapshotPath, null); 26 | snapModule.load(snapshotPath); 27 | return snapModule.exports; 28 | } 29 | 30 | injectLoader(loadSnapshotMock); 31 | 32 | 33 | const expect = Unexpected.clone() 34 | .use(UnexpectedPreact) 35 | .use(UnexpectedSinon); 36 | 37 | expect.output.preferredWidth = 80; 38 | 39 | Promise.promisifyAll(fs); 40 | 41 | const fixtures = {}; 42 | 43 | function renderIntoDocument(element) { 44 | const container = document.createElement('div'); 45 | return Preact.render(element, container); 46 | } 47 | 48 | describe('snapshots', function () { 49 | 50 | const PATH_TO_TESTS = '/path/to/tests'; 51 | let state, removeUncheckedKeysStub; 52 | 53 | before(function (done) { 54 | fs.readdirAsync(path.join(__dirname, './fixtures')) 55 | .then(dirList => { 56 | return Promise.all(dirList.map(entry => { 57 | return fs.readFileAsync(path.join(__dirname, './fixtures', entry)) 58 | .then(data => { 59 | fixtures[path.basename(entry, '.snapshot')] = data.toString('utf-8'); 60 | }); 61 | })); 62 | }).then(() => done()) 63 | .catch(e => done(e)); 64 | }); 65 | 66 | beforeEach(function () { 67 | removeUncheckedKeysStub = Sinon.stub(); 68 | state = { 69 | testPath: '/tmp/changeme.js', 70 | currentTestName: 'foo', 71 | snapshotState: { 72 | added: 0, 73 | updated: 0, 74 | unmatched: 0, 75 | update: undefined, 76 | removeUncheckedKeys: removeUncheckedKeysStub 77 | } 78 | }; 79 | JestMatchers.setState(state); 80 | Sinon.spy(fs, 'writeFileSync'); 81 | injectStateHooks(); 82 | }); 83 | 84 | afterEach(function () { 85 | fs.writeFileSync.restore(); 86 | }); 87 | beforeEach(function () { 88 | mockFs({ 89 | [PATH_TO_TESTS + '/__snapshots__/single.spec.unexpected-snap']: fixtures.single, 90 | [PATH_TO_TESTS + '/__snapshots__/multiple.spec.unexpected-snap']: fixtures.multiple, 91 | [PATH_TO_TESTS + '/__snapshots__/multipleclasses.spec.unexpected-snap']: fixtures.multipleclasses, 92 | }); 93 | }); 94 | 95 | afterEach(function () { 96 | mockFs.restore(); 97 | }); 98 | 99 | 100 | function initState(options) { 101 | state.testPath = path.join(PATH_TO_TESTS, options.testPath); 102 | state.currentTestName = options.testName; 103 | state.unexpectedSnapshot = null; 104 | if (options.update) { 105 | state.snapshotState.update = options.update; 106 | } 107 | JestMatchers.setState(state); 108 | } 109 | 110 | it('passes a single test snapshot', function () { 111 | 112 | initState({ 113 | testPath: 'single.spec.js', 114 | testName: 'single test name' 115 | }); 116 | 117 | const renderer = renderIntoDocument(); 118 | 119 | console.log(renderer.__preactattr_) 120 | expect(renderer, 'to match snapshot'); 121 | expect(fs.writeFileSync, 'was not called') 122 | }); 123 | 124 | it('updates the `matched` count', function () { 125 | 126 | initState({ 127 | testPath: 'single.spec.js', 128 | testName: 'single test name' 129 | }); 130 | 131 | const renderer = renderIntoDocument(); 132 | 133 | expect(renderer, 'to match snapshot'); 134 | expect(state.snapshotState, 'to satisfy', { 135 | matched: 1 136 | }); 137 | }); 138 | 139 | it('passes multiple test snapshots', function () { 140 | 141 | initState({ 142 | testPath: 'single.spec.js', 143 | testName: 'single test name' 144 | }); 145 | const renderer = renderIntoDocument(); 146 | 147 | expect(renderer, 'to match snapshot'); 148 | expect(renderer, 'with event', 'click', 'to match snapshot'); 149 | expect(fs.writeFileSync, 'was not called') 150 | }); 151 | 152 | describe('for an unknown test', function () { 153 | 154 | let snapshotPath; 155 | beforeEach(function () { 156 | 157 | initState({ 158 | testPath: 'single.spec.js', 159 | testName: 'a new test' 160 | }); 161 | const renderer = renderIntoDocument(); 162 | 163 | snapshotPath = path.join(PATH_TO_TESTS, '__snapshots__/single.spec.unexpected-snap'); 164 | expect(renderer, 'to match snapshot'); 165 | }); 166 | 167 | it('writes a new snapshot', function () { 168 | 169 | // Confirm we wrote the file with the old and new entries 170 | expect(fs.writeFileSync, 'to have a call satisfying', [ 171 | snapshotPath, 172 | expect.it('to match', /exports\[`a new test 1`]/) 173 | .and('to match', /exports\[`single test name 1`]/) 174 | ]); 175 | }); 176 | 177 | it('writes a new snapshot comment', function () { 178 | 179 | // Confirm we wrote the file with the old and new entries 180 | expect(fs.writeFileSync, 'to have a call satisfying', [ 181 | snapshotPath, 182 | expect.it('to match', /\/\/ ', 222 | '', 223 | "with event 'click' to match snapshot", 224 | '', 225 | '' 229 | ].join('\n') 230 | ); 231 | }); 232 | 233 | it('matches classes in the wrong order', function () { 234 | initState({ 235 | testPath: 'multipleclasses.spec.js', 236 | testName: 'multiple classes' 237 | }); 238 | 239 | const renderer = renderIntoDocument(); 240 | expect(renderer, 'to match snapshot'); 241 | }); 242 | 243 | it('diffs missing classes', function () { 244 | initState({ 245 | testPath: 'multipleclasses.spec.js', 246 | testName: 'multiple classes' 247 | }); 248 | 249 | const renderer = renderIntoDocument(); 250 | expect(() => expect(renderer, 'to match snapshot'), 'to throw', 251 | [ 252 | 'expected', 253 | '', 254 | ' ', 257 | '', 258 | 'to match snapshot', 259 | '', 260 | '' 265 | ].join('\n')) 266 | }); 267 | 268 | it('diffs extra classes', function () { 269 | initState({ 270 | testPath: 'multipleclasses.spec.js', 271 | testName: 'multiple classes' 272 | }); 273 | 274 | const renderer = renderIntoDocument(); 275 | expect(() => expect(renderer, 'to match snapshot'), 'to throw', 276 | [ 277 | 'expected', 278 | '', 279 | ' ', 283 | '', 284 | 'to match snapshot', 285 | '', 286 | '' 291 | ].join('\n')) 292 | }); 293 | 294 | it('diffs extra attributes', function () { 295 | initState({ 296 | testPath: 'multipleclasses.spec.js', 297 | testName: 'multiple classes' 298 | }); 299 | 300 | const renderer = renderIntoDocument(); 301 | expect(() => expect(renderer, 'to match snapshot'), 'to throw', 302 | [ 303 | 'expected', 304 | '', 305 | ' ', 309 | '', 310 | 'to match snapshot', 311 | '', 312 | '' 317 | ].join('\n')) 318 | }); 319 | 320 | it('allows extra attributes with `to satisfy snapshot`', function () { 321 | initState({ 322 | testPath: 'multipleclasses.spec.js', 323 | testName: 'multiple classes' 324 | }); 325 | 326 | const renderer = renderIntoDocument(); 327 | expect(renderer, 'to satisfy snapshot'); 328 | 329 | }); 330 | 331 | it('allows extra classes with `to satisfy snapshot`', function () { 332 | initState({ 333 | testPath: 'multipleclasses.spec.js', 334 | testName: 'multiple classes' 335 | }); 336 | 337 | const renderer = renderIntoDocument(); 338 | expect(renderer, 'to satisfy snapshot'); 339 | }); 340 | 341 | it('allows extra classes with `to satisfy snapshot` after event ', function () { 342 | initState({ 343 | testPath: 'multipleclasses.spec.js', 344 | testName: 'multiple classes click' 345 | }); 346 | 347 | const renderer = renderIntoDocument(); 348 | expect(renderer, 'with event click', 'to satisfy snapshot'); 349 | }); 350 | 351 | it('increments `unmatched` when a snapshot doesn`t match', function () { 352 | initState({ 353 | testPath: 'single.spec.js', 354 | testName: 'single test name' 355 | }); 356 | const renderer = renderIntoDocument(); 357 | expect( 358 | () => expect(renderer, 'with event click', 'to match snapshot'), 359 | 'to throw'); 360 | expect(state, 'to satisfy', { 361 | snapshotState: { 362 | unmatched: 1 363 | } 364 | }); 365 | }); 366 | 367 | describe('when update is true and the snapshot matches', function () { 368 | 369 | beforeEach(function () { 370 | initState({ 371 | testPath: 'single.spec.js', 372 | testName: 'single test name', 373 | update: true 374 | }); 375 | const renderer = renderIntoDocument(); 376 | expect(renderer, 'to match snapshot'); 377 | }); 378 | 379 | it('increments `matched`', function () { 380 | 381 | expect(state, 'to satisfy', { 382 | snapshotState: { 383 | updated: 0, 384 | added: 0, 385 | matched: 1, 386 | update: true 387 | } 388 | }); 389 | }); 390 | }); 391 | 392 | describe('when update is true and the snapshot doesn`t match', function () { 393 | 394 | let snapshotPath; 395 | beforeEach(function () { 396 | initState({ 397 | testPath: 'single.spec.js', 398 | testName: 'single test name', 399 | update: true 400 | }); 401 | const renderer = renderIntoDocument(); 402 | snapshotPath = path.join(PATH_TO_TESTS, '__snapshots__/single.spec.unexpected-snap'); 403 | expect(renderer, 'with event click', 'to match snapshot'); 404 | }); 405 | 406 | it('increments `updated`', function () { 407 | 408 | expect(state, 'to satisfy', { 409 | snapshotState: { 410 | updated: 1, 411 | added: 0, 412 | update: true 413 | } 414 | }); 415 | }); 416 | 417 | it('writes the new snapshot', function () { 418 | expect(fs.writeFileSync, 'to have calls satisfying', [ 419 | [ 420 | snapshotPath, 421 | expect.it('to match', /exports\[`single test name 1`]/) 422 | ] 423 | ]); 424 | }); 425 | 426 | it('writes the new snapshot comment', function () { 427 | expect(fs.writeFileSync, 'to have calls satisfying', [ 428 | [ 429 | snapshotPath, 430 | expect.it('to match', /\/\/ ', 515 | '', 516 | 'to match snapshot', 517 | '', 518 | '' 531 | ].join('\n') 532 | ); 533 | }); 534 | }); 535 | 536 | describe('removing unused keys', function () { 537 | 538 | it('removes the unused snapshot file when removeUnusedKeys is called', function () { 539 | initState({ 540 | testPath: 'single.spec.js', 541 | testName: 'single test name', 542 | update: true 543 | }); 544 | Sinon.spy(fs, 'unlinkSync'); 545 | 546 | try { 547 | // removeUncheckedKeys is called by Jest when update is true 548 | state.snapshotState.removeUncheckedKeys(); 549 | expect(fs.unlinkSync, 'to have calls satisfying', [ 550 | [path.join(PATH_TO_TESTS, '__snapshots__/single.spec.unexpected-snap')] 551 | ]); 552 | } finally { 553 | fs.unlinkSync.restore(); 554 | } 555 | 556 | }); 557 | 558 | it('removes the unused keys of a test where only some are used', function () { 559 | 560 | initState({ 561 | testPath: 'multiple.spec.js', 562 | testName: 'multi test two', 563 | update: true 564 | }); 565 | 566 | const renderer = renderIntoDocument(); 567 | expect(renderer, 'with event click', 'to match snapshot'); 568 | 569 | const originalSnapshot = loadSnapshotMock(path.join(PATH_TO_TESTS, '__snapshots__/multiple.spec.unexpected-snap')); 570 | 571 | state.snapshotState.removeUncheckedKeys(); 572 | const newSnapshot = loadSnapshotMock(path.join(PATH_TO_TESTS, '__snapshots__/multiple.spec.unexpected-snap')); 573 | 574 | expect(Object.keys(originalSnapshot), 'to equal', [ 'multi test one 1', 'multi test two 1', 'multi test two 2' ]); 575 | expect(Object.keys(newSnapshot), 'to equal', [ 'multi test two 1']); 576 | }); 577 | 578 | it('calls the original removeUncheckedKeys', function () { 579 | initState({ 580 | testPath: 'multiple.spec.js', 581 | testName: 'multi test two', 582 | update: true 583 | }); 584 | 585 | const renderer = renderIntoDocument(); 586 | expect(renderer, 'with event click', 'to match snapshot'); 587 | 588 | state.snapshotState.removeUncheckedKeys(); 589 | 590 | expect(removeUncheckedKeysStub, 'to have calls satisfying', [ 591 | { args: [], 'this': state.snapshotState } 592 | ]); 593 | }); 594 | }); 595 | 596 | it('leaves snapshots of tests that failed', function () { 597 | 598 | initState({ 599 | testPath: 'multiple.spec.js', 600 | testName: 'multi test two' 601 | }); 602 | 603 | const renderer = renderIntoDocument(); 604 | expect( 605 | () => expect(renderer, 'to match snapshot'), 606 | 'to throw'); 607 | 608 | const originalSnapshot = loadSnapshotMock(path.join(PATH_TO_TESTS, '__snapshots__/multiple.spec.unexpected-snap')); 609 | 610 | state.snapshotState.removeUncheckedKeys(); 611 | const newSnapshot = loadSnapshotMock(path.join(PATH_TO_TESTS, '__snapshots__/multiple.spec.unexpected-snap')); 612 | 613 | expect(Object.keys(originalSnapshot), 'to equal', [ 'multi test one 1', 'multi test two 1', 'multi test two 2' ]); 614 | expect(Object.keys(newSnapshot), 'to equal', [ 'multi test two 1', 'multi test two 2' ]); 615 | }); 616 | 617 | it('removes a file if there are no snapshots', function () { 618 | 619 | initState({ 620 | testPath: 'multiple.spec.js', 621 | testName: 'multi test two' 622 | }); 623 | Sinon.spy(fs, 'unlinkSync'); 624 | try { 625 | state.snapshotState.removeUncheckedKeys(); 626 | expect(fs.unlinkSync, 'to have calls satisfying', function () { 627 | fs.unlinkSync(path.join(PATH_TO_TESTS, '__snapshots__/multiple.spec.unexpected-snap')); 628 | }); 629 | } finally { 630 | fs.unlinkSync.restore(); 631 | } 632 | }); 633 | 634 | }); 635 | 636 | 637 | -------------------------------------------------------------------------------- /src/types/snapshotFunctionType.js: -------------------------------------------------------------------------------- 1 | import { FUNCTION_ID } from '../helpers/snapshots'; 2 | 3 | function installInto(expect) { 4 | 5 | expect.addType({ 6 | name: 'jest-snapshot-function', 7 | base: 'object', 8 | identify: function (value) { 9 | return value && typeof value === 'object' && value.$functype === FUNCTION_ID; 10 | }, 11 | inspect: function (value, depth, output) { 12 | return output.clone().text('function ').cyan(value.name).text('(').text(value.args).text(') { /* function body */ }') 13 | } 14 | }); 15 | } 16 | 17 | module.exports = { installInto }; 18 | -------------------------------------------------------------------------------- /src/types/types.js: -------------------------------------------------------------------------------- 1 | 2 | import { h } from 'preact'; 3 | import UnexpectedHtmlLike from 'unexpected-htmllike'; 4 | import RawAdapter from 'unexpected-htmllike-raw-adapter'; 5 | import PreactElementAdapter from 'unexpected-htmllike-preact-adapter'; 6 | import PreactRenderedAdapter from 'unexpected-htmllike-preactrendered-adapter'; 7 | 8 | const PreactVNode = Object.getPrototypeOf(h('div')); 9 | 10 | function installInto(expect) { 11 | 12 | const preactRenderedAdapter = new PreactRenderedAdapter({ includeKeyProp: true, convertToString: true, concatTextContent: true }); 13 | const htmlLikePreactRendered = UnexpectedHtmlLike(preactRenderedAdapter); 14 | const rawAdapter = new RawAdapter({ convertToString: true, concatTextContent: true }); 15 | const htmlLikeRaw = UnexpectedHtmlLike(rawAdapter); 16 | const preactAdapter = new PreactElementAdapter({ includeKeyProp: true }); 17 | const htmlLikePreactElement = UnexpectedHtmlLike(preactAdapter); 18 | 19 | expect.addType({ 20 | 21 | name: 'RenderedPreactElement', 22 | 23 | base: 'object', 24 | identify(value) { 25 | return typeof value === 'object' && 26 | value !== null && 27 | (typeof value._component === 'object' || 28 | typeof value.__preact_attr_ === 'object' || 29 | (value.base && // Following for component instance returned from preact-compat render 30 | value.hasOwnProperty('props') && 31 | value.hasOwnProperty('context') && 32 | typeof value.setState === 'function')); 33 | }, 34 | 35 | inspect(value, depth, output, inspect) { 36 | return htmlLikePreactRendered.inspect(PreactRenderedAdapter.wrapNode(value), depth, output, inspect); 37 | } 38 | }); 39 | 40 | expect.addType({ 41 | name: 'RenderedPreactElementWrapper', 42 | 43 | identify(value) { 44 | return typeof value === 'object' && 45 | value !== null && 46 | (value.type === PreactRenderedAdapter.COMPONENT_TYPE || 47 | value.type === PreactRenderedAdapter.NODE_TYPE 48 | ); 49 | }, 50 | 51 | inspect(value, depth, output, inspect) { 52 | return htmlLikePreactRendered.inspect(value, depth, output, inspect); 53 | } 54 | 55 | }); 56 | 57 | 58 | expect.addType({ 59 | name: 'PreactElement', 60 | 61 | identify: function (value) { 62 | return (typeof value === 'object' && 63 | value !== null && 64 | Object.getPrototypeOf(value) === PreactVNode); 65 | }, 66 | 67 | inspect: function (value, depth, output, inspect) { 68 | return htmlLikePreactElement.inspect(value, depth, output, inspect); 69 | } 70 | }); 71 | 72 | expect.addType({ 73 | name: 'ReactRawObjectElement', 74 | base: 'object', 75 | identify: function (value) { 76 | return rawAdapter.isRawElement(value); 77 | }, 78 | 79 | inspect: function (value, depth, output, inspect) { 80 | return htmlLikeRaw.inspect(value, depth, output, inspect); 81 | } 82 | }); 83 | } 84 | 85 | export default { installInto }; 86 | -------------------------------------------------------------------------------- /src/unexpected-preact.js: -------------------------------------------------------------------------------- 1 | import types from './types/types'; 2 | import * as deepAssertions from './assertions/deepAssertions'; 3 | 4 | 5 | const Preact = require('preact'); 6 | Preact.options.debounceRendering = function (fn) { return fn(); }; 7 | 8 | 9 | 10 | module.exports = { 11 | name: 'unexpected-preact', 12 | 13 | installInto(expect) { 14 | 15 | expect.installPlugin(require('magicpen-prism')); 16 | 17 | types.installInto(expect); 18 | deepAssertions.installInto(expect); 19 | 20 | 21 | expect.addAssertion([ 22 | ' to match snapshot', 23 | ' to match snapshot', 24 | ' to satisfy snapshot', 25 | ' to satisfy snapshot' 26 | ], 27 | function (expect) { 28 | expect.errorMode = 'bubble'; 29 | expect.fail({ 30 | message: function (output) { 31 | return output.error('To use snapshot assertions in jest, use \'unexpected-preact/jest\' to require or import unexpected-preact'); 32 | }, 33 | diff: function (output) { 34 | return output.error('To use snapshot assertions in jest, use \'unexpected-preact/jest\' to require or import unexpected-preact'); 35 | } 36 | }); 37 | } 38 | ); 39 | }, 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var Babel = require('babel-core'); 4 | 5 | module.exports = function (wallaby) { 6 | 7 | return { 8 | files: [ 9 | { 10 | pattern: 'src/tests/fixtures/**/*.js', 11 | instrument: false 12 | }, 13 | { 14 | pattern: 'src/tests/fixtures/**/*.snapshot', 15 | instrument: false 16 | }, 17 | 'src/**/*.js', 18 | { 19 | pattern: 'src/tests/**/*.spec.js', 20 | ignore: true 21 | }, 22 | ], 23 | 24 | tests: ['src/tests/**/*.spec.js'], 25 | env: { 26 | type: 'node', 27 | runner: 'node' 28 | }, 29 | 30 | compilers: { 31 | 'src/**/*.js': wallaby.compilers.babel({ 32 | babel: Babel 33 | }), 34 | 'src/**/*.jsx': wallaby.compilers.babel({ 35 | babel: Babel, 36 | "presets": [ "es2015" ], 37 | "plugins": [ ["transform-react-jsx", { "pragma": "h" } ] ] 38 | }) 39 | } 40 | }; 41 | }; 42 | --------------------------------------------------------------------------------