UNPKG

6.06 kBJavaScriptView Raw
1/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2015 - present Instructure, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24const ReactDOM = require('react-dom')
25const sinon = require('sinon')
26const { cloneElement } = require('react')
27const { StyleSheet } = require('glamor/lib/sheet')
28
29const { ReactWrapper, mount } = require('./enzymeWrapper')
30
31const realSetTimeout = setTimeout
32
33const override = function (object, methodName, extra) {
34 // eslint-disable-next-line no-param-reassign, wrap-iife
35 object[methodName] = (function (original, after) {
36 return function () {
37 const result = original && original.apply(this, arguments)
38 after.apply(this, arguments)
39 return result
40 }
41 })(object[methodName], extra)
42}
43
44class Testbed {
45 constructor (subject) {
46 this.subject = subject
47 this.sandbox = sinon.createSandbox()
48
49 beforeEach(this.setup.bind(this))
50 afterEach(this.setup.bind(this))
51 }
52
53 get wrapper () {
54 return new ReactWrapper(this.rootNode, true)
55 }
56
57 tick (ms = 300) {
58 this.sandbox.clock.tick(ms)
59 }
60
61 raf () {
62 this.sandbox.clock.runToFrame()
63 }
64
65 stub (obj, method, fn) {
66 if (typeof fn === 'function') {
67 return this.sandbox.stub(obj, method).callsFake(fn)
68 } else {
69 return this.sandbox.stub(obj, method)
70 }
71 }
72
73 spy (obj, method) {
74 return this.sandbox.spy(obj, method)
75 }
76
77 defer () {
78 return realSetTimeout.apply(window, arguments)
79 }
80
81 disableCSSTransitions () {
82 document.body.classList.add('no-transition')
83 }
84
85 enableCSSTransitions () {
86 document.body.classList.remove('no-transition')
87 }
88
89 setup () {
90 this.teardown()
91
92 // so that we can test for prop type validation errors in our tests:
93 if (typeof console.error === 'function') {
94 this._originalConsoleError = console.error
95 console.error = (firstMessage, ...rest) => {
96 if (typeof firstMessage === 'string' && firstMessage.startsWith('Warning:')) {
97 throw new Error(firstMessage)
98 }
99 return this._originalConsoleError(firstMessage, ...rest)
100 }
101 }
102
103 document.documentElement.setAttribute('dir', 'ltr')
104 document.documentElement.setAttribute('lang', 'en-US')
105
106 this.rootNode = document.createElement('div')
107 this.rootNode.setAttribute('data-ui-testbed', 'true')
108 document.body.appendChild(this.rootNode)
109
110 this.disableCSSTransitions()
111
112 this.sandbox.useFakeTimers()
113 }
114
115 teardown () {
116 this.sandbox.resetHistory()
117 this.sandbox.restore()
118
119 if (this._originalConsoleError) {
120 console.error = this._originalConsoleError
121 }
122
123 this.unmount()
124
125 this.rootNode && this.rootNode.parentNode && this.rootNode.parentNode.removeChild(this.rootNode)
126 this.rootNode = document.querySelector('[data-ui-testbed]')
127 this.rootNode && this.rootNode.parentNode && this.rootNode.parentNode.removeChild(this.rootNode)
128 this.rootNode = null
129 }
130
131 unmount () {
132 if (this.$instance && typeof this.$instance.unmount === 'function') {
133 this.$instance.unmount()
134 }
135
136 this.rootNode && ReactDOM.unmountComponentAtNode(this.rootNode)
137 this.$instance = null
138 }
139
140 setTextDirection (dir) {
141 document.documentElement.setAttribute('dir', dir)
142 }
143
144 render (props = {}, context) {
145 if (!this.subject) {
146 return
147 }
148
149 if (this.$instance) {
150 this.unmount()
151 }
152
153 const subject = cloneElement(this.subject, Object.assign({}, this.subject.props, props))
154
155 this.$instance = mount(subject, { attachTo: this.rootNode, context })
156
157 // axe uses setTimeout so we need to call clock.tick here too
158 override(ReactWrapper.prototype, 'getA11yViolations', () => {
159 this.sandbox.clock && this.sandbox.clock.tick(1000)
160 })
161
162 return this.$instance
163 }
164}
165
166Testbed.init = () => {
167 /* eslint-disable no-console */
168 console.log('[ui-testbed] Initializing test bed...') // eslint-disable-line no-console
169
170 // clear the console before rebundling:
171 if (typeof console.clear === 'function') {
172 console.clear()
173 }
174 /* eslint-enable no-console */
175
176 process.once('unhandledRejection', (error) => {
177 console.error('Unhandled rejection: ' + error.stack)
178 process.exit(1)
179 })
180
181 const sheet = new StyleSheet({ speedy: true, maxLength: 40 })
182
183 sheet.inject()
184 sheet.insert(`
185 .no-transition * {
186 transition-property: none !important;
187 -o-transition-property: none !important;
188 -moz-transition-property: none !important;
189 -ms-transition-property: none !important;
190 -webkit-transition-property: none !important;
191
192 transform: none !important;
193 -o-transform: none !important;
194 -moz-transform: none !important;
195 -ms-transform: none !important;
196 -webkit-transform: none !important;
197
198 animation: none !important;
199 -o-animation: none !important;
200 -moz-animation: none !important;
201 -ms-animation: none !important;
202 -webkit-animation: none !important;
203 }
204 `)
205}
206
207Testbed.wrap = (element) => {
208 return new ReactWrapper(element, true)
209}
210
211module.exports = Testbed