Closed GnanaAjana closed 5 years ago
I don't think jsdom actually renders your component so the scroll height might not change even when dispatching the scroll event
@weyert I checked to print log for this.elementRef.current
, its innerHTML has the content which i bind using dangerouslySetInnerHTML={createMarkup(testHtml)}
but other height related is 0. ({height: 0, width: 0, offsetHeight: 0, clientHeight: 0, bottom: 0, top: 0 })
In above I missed this.
<div id="inner-content"> // this is scrollable element
<div ref={this.elementRef} dangerouslySetInnerHTML={createMarkup(testHtml)}></div>
</div>
I'm note sure, but I think this is because maybe jsdom does not exports this as you may be expecting.
In some cases may be useful to mock this implementation with Jest. https://jestjs.io/docs/en/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Them you can export something like:
window.mockSize = {height: 0, width: 0, scrollHeight: 0, offsetLeft: 0, offsetTop: 0, offsetHeight: 0 }
Them changes the mocked values to return expected values to trigger the behavior as your test requires.
Don't know if it is the best way, but usually mocking with jest solves this kind of problem to me.
For offsetHeight, scrollHeight
to change when using jsdom
, then this library would need to actually do layouting which it currently doesn't do so these values wouldn't change. I guess you would need to run the tests in the browser to test this or maybe mock the properties accordingly.
jsdom doesn't support layout. This means measurements like this will always return 0
as it does here.
You have three options:
I typically recommend option 3. Good luck!
Do you have an example on how to mock these values?
Mock the values = get a reference to an element and set the value of the attribute
Nothing to so with jest mocks or anything
I use like this, so I can mock the offsetHeight for example
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight')
const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth')
beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 500 })
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 500 })
})
afterAll(() => {
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight)
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', originalOffsetWidth)
})
describe('You can test with mocked values here', () => {
//offsetHeight = 500
//offsetWidth = 500
})
I have this in one of my tests:
import matchMediaPolyfill from 'mq-polyfill'
// ...
beforeAll(() => {
matchMediaPolyfill(window)
window.resizeTo = function resizeTo(width, height) {
Object.assign(this, {
innerWidth: width,
innerHeight: height,
outerWidth: width,
outerHeight: height,
}).dispatchEvent(new this.Event('resize'))
}
})
// ... then in my test window.resizeTo(800, 300)
I think if you're not going to use Cypress/a real browser, this is probably the recommended way to do this. We should probably add this to the examples: https://github.com/kentcdodds/react-testing-library-examples
I use like this, so I can mock the offsetHeight for example
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight') const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth') beforeAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 500 }) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 500 }) }) afterAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', originalOffsetWidth) }) describe('You can test with mocked values here', () => { //offsetHeight = 500 //offsetWidth = 500 })
This solution just assign for all element, but how to mock
offset
for each element? Please tell me if you have an idea for it. Thanks for your solution.
I use like this, so I can mock the offsetHeight for example
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight') const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth') beforeAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 500 }) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 500 }) }) afterAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', originalOffsetWidth) }) describe('You can test with mocked values here', () => { //offsetHeight = 500 //offsetWidth = 500 })
This solution just assign for all element, but how to mock
offset
for each element? Please tell me if you have an idea for it. Thank your solution.
I think the same as you can use HTMLElement.prototype
you may be able to use it in a html node using a query selector like document.getElementById(id)
or you can use a dom-testing-library
selector like getByText
and insert this prop in the runtime.
I did not test this but I think it should work fine.
I use like this, so I can mock the offsetHeight for example
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight') const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth') beforeAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 500 }) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 500 }) }) afterAll(() => { Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight) Object.defineProperty(HTMLElement.prototype, 'offsetWidth', originalOffsetWidth) }) describe('You can test with mocked values here', () => { //offsetHeight = 500 //offsetWidth = 500 })
This solution just assign for all element, but how to mock
offset
for each element? Please tell me if you have an idea for it. Thank your solution.I think the same as you can use
HTMLElement.prototype
you may be able to use it in a html node using a query selector likedocument.getElementById(id)
or you can use adom-testing-library
selector likegetByText
and insert this prop in the runtime. I did not test this but I think it should work fine.
Tks for reply
. I've implemented const el: HTMLSpanElement = document.createElement('span'); Object.defineProperty(el, 'offsetTop', { configurable: true, value: index });
and it work.
So I recently made a very tiny window-resizeto
testing polyfill to help simplify resize tests. Seems to be one of the topics of this issue, so thought I might share here.
If you're using Jest, it's pretty easy to add:
jest.config.js
module.exports = {
setupFilesAfterEnv: [
// polyfill window.resizeTo
'window-resizeto/polyfill'
]
}
some-test.spec.js
window.resizeTo(500, 500)
// window is now resize to 500x500
If you wanted to more selectively add it to only certain test suites, you could also do:
import 'window-resizeto/polyfill'
Or if you wanted to just use the ponyfill exactly where needed, you could do:
import { resizeTo } from 'window-resizeto'
// ...
test('something', () => {
resizeTo(window, 500, 500);
// window is now resized to 500x500
})
All of these use-cases are covered in the docs
jsdom doesn't support layout. This means measurements like this will always return
0
as it does here.You have three options:
- Put the logic that uses those measurements in a function and test it in isolation.
- Mock the values.
- Use Cypress (+ cypress-testing-library) to test that particular situation
I typically recommend option 3. Good luck!
@kentcdodds I have run into the similar problem, I want to test a Component with limited style like min-width
, but I found that the dom return by querySelector
has a zero value with clientWidth
and clientHeight
property.
as you say jsdom doesn't support layout, so there is no possibility to acquire the dom width or height, right?
Correct.
@kentcdodds But what if my component appends some text in a div and calculates the clientHeight dynamically?
Then you'll have to use something other than jsdom to test it. There's a testing library for Cypress. That's a good option.
In jest it's possible to spy getters like the following
jest
.spyOn(element, 'clientHeight', 'get')
.mockImplementation(() => height);
docs: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname-accesstype reference: https://stackoverflow.com/a/56457850/863110
Personally, I prefer spy (which can be restore
d afterEach
) rather then override properties (which can't be restore
d afterEach
, only set the value "back". Although, this probably what jest does under the hood..)
If someone is looking for a sinon solution, you can defines a new getter:
sinon.stub(element, 'clientHeight').get(() => height);
A bit late but... things like this always make me go back to good old Karma ;)
I came up with this util :
/**
* Example: properties can be:
*
* properties = {
* scrollWidth: 1000,
* }
*/
export const defineHtmlRefProperties = (properties: Record<string, unknown>) => {
const originalValues: Record<string, unknown> = {};
Object.keys(properties).forEach(key => {
originalValues[key] = Object.getOwnPropertyDescriptor(HTMLElement.prototype, key);
});
const setHTMLProperties = () => {
Object.entries(properties).forEach(([key, value]) => {
Object.defineProperty(HTMLElement.prototype, key, {
configurable: true,
value,
});
});
};
const unsetHTMLProperties = () => {
Object.keys(properties).forEach(key => {
if (originalValues[key]) {
// @ts-ignore
Object.defineProperty(HTMLElement.prototype, key, originalValues[key]);
}
});
};
return {
setHTMLProperties,
unsetHTMLProperties,
};
};
And then you can use it like that :
const { setHTMLProperties, unsetHTMLProperties } = defineHtmlRefProperties({
scrollWidth: 1000,
});
describe('Dummy', () => {
beforeAll(() => {
…
setHTMLProperties();
});
afterAll(() => {
…
unsetHTMLProperties();
});
Unit Tests are the best guard against regressions. Using E2E testing is an option, but it's not ideal and requires compiler time and/or continuous integration. At the time a bug may be found there will be confusion as a lot of QA and infra teams are not in the same room so to speak. Not supporting common browser APIs is a miss here, and it's not really react-testing-library that's the root cause. I tried the above solutions and none work for my simple scenario.
In component js,
Based on scrollHeight am displaying footer or binding scroll event. So, in this case, scrollHeight is always 0.
In this case, due to clientHeight 0, I can't able to cover the scroll event test case.
Am not using 'enzyme' here.
Is there any other way to get clientHeight.