์๋ ํ์ธ์ @cpojer ๋
์ด๊ฒ์ ์ค์ ๋ก jsdom@8
๋ฌธ์ ์ ๋ ๊ฐ๊น์ต๋๋ค... tmpvar/jsdom#1388์ ์ฐธ์กฐํ์ญ์์ค. ๊ทธ๋ฌ๋ Jest๊ฐ jsdom์ด ์ ๊ณตํ๋ ์๋ฃจ์
์ ์ ํํ๋๋ก ์ฌ๊ธฐ์๋ ๊ณ ์ ํ๊ณ ์ถ์ต๋๋ค.
์ด์ ์๋ [email protected]/[email protected]
๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ ํ
์คํธ๋ฅผ ์์ฑํ ์ ์์์ต๋๋ค.
jest.autoMockOff()
jest.setMock('../lib/window', window)
jest.mock('cookies-js')
jest.mock('query-string')
jest.mock('superagent')
describe(['@utils/auth - When an AJAX response returns:'
].join('')
, function () {
beforeEach(function () {
window.location.href = 'http://quux.default.com'
var queryString = require('query-string')
queryString.__setMockParseReturns([{
'access_token': '1234foo',
'expires_in': '9999'
}])
})
it(['should set a redirect token and goto platform ',
'when the AJAX request returns 401.'
].join('')
, function () {
var superagent = require('superagent')
superagent.__setMockAjaxResponses([
[null, { 'status': 401 }]
])
var href = window.location.href
var auth = require('../index.js')
auth.login(function (res) {})
var Cookies = require('cookies-js')
var config = require.requireActual('../config')
expect(decodeURIComponent(window.location.href)).toBe([
config.loginUrl,
config.loginServiceLogin,
'?client_id=',
config.clientId,
'&response_type=token',
'&redirect_uri=',
config.clientRedirectUri
].join(''))
expect(Cookies.__getMockCookieData()[config.clientId + '_state_locationAfterLogin']).toBe(escape(href))
})
๊ทธ๋ฆฌ๊ณ ๊ทธ ์ํ์ ํต๊ณผํ ๊ฒ์
๋๋ค. jsdom@8
์ดํ๋ก ์ด๊ฒ์ ๋ ์ด์ ๊ฐ๋ฅํ์ง ์์ผ๋ฉฐ ์ด๋ฌํ ํ
์คํธ๋ ์คํจํฉ๋๋ค.
jsdom์ด ์ด๋ค ์ ํ์ ๊ธฐ๋ฅ์ ์ฐพ๊ณ ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง Jest๊ฐ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ํด๋น ๊ธฐ๋ฅ์ ์ ํํ๋๋ก ํ๊ณ ์ถ์์ต๋๋ค.
๋น์ ์ด ๋ง์ต๋๋ค. ์ด๊ฒ์ ์ค์ ๋ก jsdom ๋ฌธ์ ์ ๋๋ค. Facebook์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํํ ์์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Object.defineProperty(window.location, 'href', {
writable: true,
value: 'some url'
});
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ํจ๊ณผ์ ์ด์ง๋ง ๋ด๋ถ์ ์ผ๋ก๋ ์ฌ์ ํ jsdom 7์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
Object.defineProperty
์์
๋ฐฉ์์ด ๊ด์ฐฎ๋ค๊ณ ์๊ฐํ๋ฏ๋ก ์ด ์์
์ ๋ซ๊ฒ ์ต๋๋ค. jsdom 8์์ ์๋ํ์ง ์๋๋ค๋ฉด ๊ธฐ๊บผ์ด ๋ค์ ์ด๊ฒ ์ต๋๋ค.
์ฟจ, ๊ฐ์ฌํฉ๋๋ค. ์๋ํด ๋ณด๊ฒ ์ต๋๋ค.
@cpojer , ์ด ๋ฌธ์ ๋ฅผ ๋ค์ ์ด๋ ค๋ฉด ๋ฌด์์ ํด๋ฆญํด์ผ ํ๋์ง ์ ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค...
์ด์จ๋ jest
ํ๊ฒฝ์ ์ฌ๊ธฐ์ ์ค๋ช
๋ ๋๋ก jsdom.changeUrl(window, url)
๋ฅผ ํธ์ถํ ์ ์์ต๋๊น? [email protected]
์ ์ฐฝ ์ธ์คํด์ค?
์ค๋๋ ํฐ์ผ์ด์ง๋ง ์ฌ์ ํ ์ด ๋ฌธ์ ๊ฐ ์๋ ์ฌ๋๋ค์ ์ํด ๋์ window.location.assign()
๋ฅผ ์ฌ์ฉํ๊ธฐ ์์ํ์ต๋๋ค. ๊ทธ๋์ ํ
์คํธ์์ assign
ํจ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์กฐ๋กฑํ ์ ์์ต๋๋ค.
it('will redirect with a bad route', () => {
window.location.assign = jest.fn();
const redirection = shallow(<Redirection />, {
context: {
router: {
location: {
pathname: '/wubbalubbadubdub',
},
},
},
});
expect(window.location.assign).toBeCalledWith(`${CONFIG.APP_LEGACY_ROOT}`);
});
@th3fallen ๊ฐ์ฌํฉ๋๋ค. ๋ฉ์ง๋ค!
Btw @cpojer ์ ๋ 5์ 1์ผ FB์์ ์์ํฉ๋๋ค.... ;P
๋ฉ์ง!
Mocha+Chai+Sinon.js์์ Jest๋ก ํ
์คํธ๋ฅผ ๋ง์ด๊ทธ๋ ์ด์
ํ๋ ค๊ณ ํ๋๋ฐ ํน์ ํ
์คํธ์ ์์น๋ฅผ โโ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ์ ์ ์ ์์ต๋๋ค.
Jest 19.x ๋ Object.defineProperty
ํธ๋ฆญ์ ์ฌ์ฉํ์ฌ ์์น๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ JSDom 9.12 ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ํ tmpvar/jsdom#1700์ ์ค๋ช
๋ ์ด์ ๋๋ฌธ์ jsdom.changeURL()
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@cpojer Jest์์ jsdom.changeURL()
์ ๋ํ ์ผ๋ถ ํ๋ก์ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ์ด๋ป์ต๋๊น?
@okovpashko ์ฐ๋ฆฌ๋ jsdom์ ํ๊ฒฝ์ ๋ ธ์ถํ ๊ณํ์ ๋๋ค: https://github.com/facebook/jest/issues/2460
Object.defineProperty
๋ FB์์ ์ฐ๋ฆฌ๋ฅผ ์ํด ์ผํฉ๋๋ค.
@thymikee ๋๋ ๊ทธ ๋ฌธ์ ๋ฅผ ๋ณด์์ง๋ง ๊ทธ ์ ์์ด ๊ฑฐ๋ถ๋์๋ค๊ณ ์๊ฐํ์ต๋๋ค.
@cpojer ๋๋ ๋น์ ์ ์๋ฅผ ์๋ชป ์ฝ์๊ณ ์ฌ๋๋ค์ด Object.defineProperty(window, 'location', {value: 'url'});
๋ฅผ ์ฌ์ฉํ๋๋ก ์ ์ํ ์ด ๋ฌธ์ ์ ๊ด๋ จ๋ ๋ค๋ฅธ ๊ฒ๊ณผ ํผํฉํ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค!
href ๋ฟ๋ง ์๋๋ผ ๋ณ๊ฒฝํด์ผ ํ๋ฏ๋ก ์ด ์ค๋ ๋๋ฅผ ์ฝ์ ๋๊ตฐ๊ฐ์๊ฒ ์ ์ฉํ ์ ์๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ์์ฑํ์ต๋๋ค.
const setURL = (url) => {
const parser = document.createElement('a');
parser.href = url;
['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
Object.defineProperty(window.location, prop, {
value: parser[prop],
writable: true,
});
});
};
์ด ์ค๋ ๋๋ฅผ ๋ ๋์ด์ ์ฃ์กํ์ง๋ง ์ ์๋ ๋๋ก ํธ์ ๊ธฐ๋ฅ์ ์กฐ๋กฑํ๋ ค๊ณ ํ์ต๋๋ค...
reactRouterReduxMock.push = (url) => {
Object.defineProperty(window.location, 'href', {
writable: true,
value: url
})
})
ํ์ง๋ง ์ฌ์ ํ ๋ผ์ด๋ํ ์ ์๋ jsdom ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
TypeError: Cannot read property '_location' of null
at Window.location (/Users/user/projects/app/client/node_modules/jsdom/lib/jsdom/browser/Window.js:148:79)
at value (/Users/user/projects/app/client/test/integration-tests/initialSetup.js:122:32) //this is the defineProperty line above
๋๋ ์ด๊ฒ์ด jsdom ์ค๋ฅ๋ผ๋ ๊ฒ์ ์๊ณ ์์ง๋ง ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ฌ๋๋ค์ ์ํด ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋๋ก ๊ณต์ ํ ์ ์๋ ์ค์ ์ปจํ ์คํธ๊ฐ ๋ ์์ต๋๊น?
๊ฐ์ฌ ํด์
@matt-dalton https://github.com/facebook/jest/issues/890#issuecomment -295939071์์ ๋ด ์ ์์ ์๋ํด๋ณด์ญ์์ค.
@matt-dalton ๊ทํ์ URL์ ๋ฌด์์
๋๊น? jest-config.json
์ testURL์ด ์ค์ ๋์ด ์์ต๋๊น ์๋๋ฉด about:blank
๋ก ์ด๊ธฐํํฉ๋๊น?
@ianlyons ์, package.json์์ ์ด์ ๋ํด "https://test.com/"
๊ฐ์ ์ค์ ํ๋๋ฐ blank
๋ก ํ์๋๋ ๊ฒฝ๋ก๊ฐ ์์ต๋๋ค.
@th3fallen ๋ด๊ฐ ๋น์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ดํดํ๋ค๋ฉด ์ด๊ฒ์ด ๋ด ์ฌ์ฉ ์ฌ๋ก์ ์ ํฉํ์ง ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ํ ๋น์ด ํธ๋ฆฌ๊ฑฐ๋๋๋ก ํ๋ ์ปจํ ์คํธ ๊ฐ์ผ๋ก URL์ ์ ๋ฌํ๊ณ ์์ต๋๊น? ๊ธฐ๋ณธ์ ์ธ ํตํฉ ํ ์คํธ๋ฅผ ํจ๊ป ํ๋ ค๊ณ ํ๋ฏ๋ก ๋ผ์ฐํฐ๊ฐ ์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋์ ์ด๋ป๊ฒ ์๋ตํ๋์ง ํ์ธํ๊ณ ์ถ์ต๋๋ค. API ์๋ต์ ์กฐ๋กฑํ ๋ค์ ์ฑ ๋ก์ง์ ์ฌ์ฉํ์ฌ URL์ ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค(์ฆ, ์ธ๋ถ์์ ์ง์ ํธ๋ฆฌ๊ฑฐํ๊ณ ์ถ์ง ์์ต๋๋ค).
Object.defineProperty
๋ ์๋ฅผ ๋ค์ด window.location.search
์ ์์กดํ๋ ๊ธฐ๋ฅ์ ํ
์คํธํ๋ ํธ๋ฆญ์ ์ํํ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ฆ, window.location.search
๋ฅผ ๋ณ๊ฒฝํ๋ฏ๋ก ๋ค๋ฅธ ํ
์คํธ๊ฐ ์ํฅ์ ๋ฐ์ ์ ์์ต๋๋ค. Object.defineProperty
window.location.search
์ ๋ํ ๋ณ๊ฒฝ ์ฌํญ์ "์คํ ์ทจ์"ํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น? ๋ง์น ๋๋ด ๋ชจ์ ํจ์์ mockReset
๊ธฐ๋ฅ์ด ์๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
@msholty-fd ์ด ์ ๊ทผ ๋ฐฉ์์ ์๋ํ ์ ์์ต๋๋ค.
const origLocation = document.location.href;
let location = origLocation;
beforeAll(() => {
const parser = document.createElement('a');
['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
Object.defineProperty(window.location, prop, {
get: function() {
parser.href = location;
return parser[prop];
}
});
});
});
afterEach(() => {
location = origLocation;
});
test('location 1', () => {
location = "https://www.google.com/";
console.log(document.location.href); // https://www.google.com/
});
test('location 2', () => {
console.log(document.location.href); // about:blank
});
Jest 22.0.1์์ ์๋์ด ์ค์ง๋์์ต๋๋ค.
Object.defineProperty(window.location, 'href', {
writable: true,
value: 'some url'
});
์๋ฌ ๋ฉ์์ง:
TypeError: Cannot redefine property: href
at Function.defineProperty (<anonymous>)
ํ , ์ด๋ป๊ฒ๋ ์ฌ๋๋ค์ด reconfigure
์ ํธ์ถํ๋๋ก ํ์ฉํด์ผ ํ ์๋ ์์ต๋๋ค. https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring -the-jsdom-with-reconfiguresettings
์ด ๋ฌธ์ ๊ฐ ์ข ๋ฃ๋์์ผ๋ฏ๋ก ์ด์ ๊ด๋ จ๋ ์ ๋ฌธ์ ๋ฅผ ์ด์์ต๋๋ค. #5124
@SimenB Jest๊ฐ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ผ ํ๋ค๊ณ ํ์ ํ์ง ์์ต๋๋ค. JSDOM์ window.location.assign()
๊ฐ ์๋ํ ๋๋ก ์๋ํ๋๋ก ํ์ฉํ๊ณ window.location.href
๋ฑ์ ์ถ๋ ฅ์ ์ฌ๊ตฌ์ฑํด์ผ ํฉ๋๋ค.
jsdom์ ๊ธฐ๋ณธ URL์ด about:blank
๋ก ๊ธฐ๋ณธ ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL
๋ฅผ ์ป์์ต๋๋ค.
jsdom
<base href='your_base_url' />
jest
URL์ ํ ๋นํ๋ ค๊ณ ์๋ ํ์ง๋ง ์ฑ๊ณตํ์ง ๋ชปํ ์ฑ 4์๊ฐ์ ๋ณด๋์ต๋๋ค. jest
, ๋ด๊ฐ ์๋๋ฏ๋ก ํฌ๊ธฐํ์ต๋๋ค.
Object.defineProperty
์๋ฃจ์
์ jsdom
jsdom
์ด์ ๋ฒ์ ์์๋ง ์๋ํฉ๋๋ค.
jsdom
ver > 10์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ @th3fallen ์ด ์ธ๊ธํ ๊ฒ์ฒ๋ผ ์ฌ๋ฐ๋ฅธ ์๋ฃจ์
์
๋๋ค.
window.location.assign
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์
๋๋ค.
about:blank
์ด์ธ์ ๋ค๋ฅธ URL์ ์ํ๋ฉด testURL
๊ตฌ์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@SimenB๋ ๋ต๋ณ ๊ฐ์ฌํฉ๋๋ค.
์๋์, ์ ๋ url
base url
์ ๋ํด ์ด์ผ๊ธฐํ๊ณ ์์์ต๋๋ค. window.location.href="/login"
๋ฅผ ์ํํ ์ฝ๋๊ฐ ์๊ณ jest
์คํํ ๋ jsdom
/login
๊ฐ ์ ํจํ URL์ด ์๋๋ผ๋ ์์ธ๋ฅผ throwํฉ๋๋ค.
TypeError: Could not parse "/login" as a URL
jsdom
์ ์์ค ์ฝ๋๋ฅผ ํ์ธํ๊ณ ์ด๊ฒ์ด ๊ธฐ๋ณธ URL ์ค์ ์ด ์๊ธฐ ๋๋ฌธ์ด๋ผ๋ ๊ฒ์ ๊นจ๋ฌ์์ต๋๋ค(๊ธฐ๋ณธ ์ฃผ์ ์์ด ๋ธ๋ผ์ฐ์ URL ํ์์ค์ "/login"์ ์
๋ ฅํ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค).
jsdom
๋ฅผ ์ฌ์ฉํ๋ฉด ์ผ๋ฐ์ ์ผ๋ก ๋ค์์ ํตํด ๊ธฐ๋ณธ URL์ ์ค์ ํ ์ ์์ต๋๋ค.
global.jsdom = new JSDOM('<html><head> <base href="base_url" /></head></html>')
ํ์ง๋ง jest
๊ฐ jsdom
$ ๋ฅผ ์ค์ ํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ํต์ ํ ์ ์์ต๋๋ค.
--- ์
๋ฐ์ดํธ: jsdom
๋ฅผ ์ข
์์ฑ์ผ๋ก ๋ช
์์ ์ผ๋ก ์ถ๊ฐํ๊ณ jsdom
์๋์ผ๋ก ๊ตฌ์ฑํ ์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์ window.location.href=
๋ฅผ window.location.assign
๋ก ๋์ฒดํ๊ณ assign
๊ธฐ๋ฅ์ ๋ชจ์ํ๋ ์๋ฃจ์
์ ์ฐพ์์ต๋๋ค.
@bochen2014 ์ด ๋ฌธ์ ์๋ ์ต์ ๋ฒ์ ์ jsdom์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ์ ๋ณด๊ฐ ์์ต๋๋ค. #5124
tl;dr: window.location.assign()
์ ์กฐ๋กฑํ๊ฑฐ๋ jest-environment-jsdom-global
๋ฅผ ์ฌ์ฉํ์ฌ ๋นํ ์ค์ jsdom์ ์ฌ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
@simon360 ๊ฐ์ฌํฉ๋๋ค
๊ทธ๊ฒ ๋ด๊ฐ ํ ์ผ์ด์ผ ;-)
ํ
์คํธ์์ ๋ค๋ฅธ ์ด๊ธฐ urls
jsdom.reconfigure
๋ฅผ ์ฌ์ฉํ๊ณ , ํ
์คํธ๊ฐ ์๋ ์ฝ๋์์ url์ ๋ณ๊ฒฝํด์ผ ํ ๋๋ง๋ค window.location.assign
๋ฅผ ์ฌ์ฉํ์ฌ ์กฐ๋กฑํ์ต๋๋ค. ๋๋ฅผ ์ํด ์ผํ.
jsdom์ ๋ํ URL์ ์ค์ ํ๊ธฐ ์ํด ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๊ฑฐ๋ ๋ฐ์ํ ์ ์๋ ์ฌ๋๋ค์ ์ํด
// jest.config.js
module.exorts={
testURL: 'http://localhost:3000',
// or :
testEnvironmentOptions: {
url: "http://localhost:3000/",
referrer: "https://example.com/",
}
}
์ด๊ฒ์ ๋ชจ๋ ํ
์คํธ์ ๋ํ URL์ ์ค์ ํฉ๋๋ค.
ํน์ ํ
์คํธ์์ ๋ค๋ฅธ URL์ ์ํ๋ฉด jsdom.reconfigure
api๋ฅผ ์ฌ์ฉํ์ญ์์ค.
๋จ์ ํ
์คํธ ์ฝ๋(์: ํ๋ก๋์
์ฝ๋) ์ธ๋ถ์์ ์ฆ์์์ url์ ๋ณ๊ฒฝํด์ผ ํ๋ ๊ฒฝ์ฐ window.location.assign
๋ฅผ ์ฌ์ฉํ๊ณ ์ด๋ฅผ ์กฐ๋กฑํด์ผ ํฉ๋๋ค.
๋ค๋ฅธ ํฐ์ผ์ ๊ฒ์ํ์ง๋ง ์ฌ๊ธฐ์ ๊ฒ์ํฉ๋๋ค.
Jest 21.2.1์ ๋ํ ์ข์ ์๋ฃจ์ ์ ์ฐพ์์ต๋๋ค.
์ข์, ์ง๊ธ๊น์ง ์ด๊ฒ์ ๋ํ ๊ฐ์ฅ ์ฌ์ด ํด๊ฒฐ์ฑ
์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Jest ์ค์ ์ผ๋ก ์ด๋ํฉ๋๋ค(์: package.json ์ฌ์ฉ).
"jest": {
"testURL": "http://localhost"
}
์ด์ ์ฐฝ ๊ฐ์ฒด์ ์ก์ธ์คํ ์ ์์ผ๋ฉฐ ํ ์คํธ ์ค์ URL์ ์ํ๋ ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค.
it('Should set href url to testURL', () => {
// Here I set href to my needs, opinionated stuff bellow
const newUrl = 'http://localhost/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
Object.defineProperty(window.location, 'href', {
writable: true,
value: newUrl
});
console.log(window.location.href);
});
it('Should set pathname url to testURL', () => {
// Here I set href to my needs, opinionated stuff bellow
const newUrl = '/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
Object.defineProperty(window.location, 'pathname', {
writable: true,
value: newUrl
});
console.log(window.location.pathname);
});
์ด๊ฒ์ด ๋๊ตฐ๊ฐ๋ฅผ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
@petar-prog91 ๋์์ด ๋์์ต๋๋ค. ํ์ง๋ง ์คํ๊ฐ ์์ต๋๋ค. $# TestURL
testURL
์ฌ์ผ ํฉ๋๋ค.
@BarthesSimpson ํต์ง, ์ ๋ฐ์ดํธ๋ ์๊ฒฌ ๊ฐ์ฌํฉ๋๋ค.
๊ฒ์๋ฅผ ์ค์งํ์ญ์์ค. ๋๋ด์ผ๋ก๋ ์๋ํ์ง ์์ต๋๋ค.": "^22.4.2"
์๋
,
๋๋ ์ด๊ฒ์ ํ
์คํธ์์ ์ฌ์ฉํ๊ณ ์ ์ญ ์ํ๋ฅผ ์ญ์ ํ๊ณ jsdom์ ์ฌ์ฉํ์ฌ ์ ์ํ๋ฅผ ๋ง๋ญ๋๋ค... :
describe('componentDidMount', () => {
delete global.window
const window = (new JSDOM(``, {url: 'https://example.org/'})).window
global.window = window
describe('When window is defined', () => {
const spy = jest.spyOn(Utils, 'extractTokenFromUrl')
it('should call extract token function with window location', () => {
mount(<Header />)
expect(spy).toHaveBeenCalledWith('https://example.org/')
})
})
})
@UserNT ํ์ธ โ TypeError: Cannot redefine property: href
๋ฅผ ์ ๊ณตํฉ๋๋ค.
@annemarie35 ๊ฐ ์๋ํ์ง ์์ต๋๋ค โ ReferenceError: JSDOM is not defined
์ด๊ฒ์ด ๋๊ตฐ๊ฐ์๊ฒ ๋์์ด ๋ ์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ด๊ฒ์ด ์ ๊ฐ ํ์ฌ ํ๊ณ ์๋ ์ผ์ ๋๋ค.
const redirectTo = (url: string): void => {
if (process.env.NODE_ENV === "test") {
global.jsdom.reconfigure({ url: `${getBaseUrl()}${url}` });
} else {
window.location.replace(url);
}
};
๋ฆฌ๋๋ ์ ๊ธฐ๋ฅ์ ์์ฑํ๊ณ ๋์ ์ฌ์ฉํ์ญ์์ค. ๋ฐ๋ผ์ ํ๊ฒฝ ํ ์คํธ์์ jsdom.reconfigure url์ ์์กดํ์ฌ url ๋ถ๋ถ์ ๋ณ๊ฒฝํฉ๋๋ค.
๋๋ ์ด๊ฒ์ ์ด๋ ๊ฒ ์ฌ์ฉํ๋ค
export const clientFetchData = (
history: Object,
routes: Object,
store: Object
) => {
const callback = location =>
match({ routes, location }, (error, redirectLocation, renderProps) => {
if (error) {
redirectTo("/500.html");
} else if (redirectLocation) {
redirectTo(redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
if (!isEmpty(window.prerenderData)) {
// Delete initial data so that subsequent data fetches can occur
window.prerenderData = undefined;
} else {
// Fetch mandatory data dependencies for 2nd route change onwards
trigger(
FETCH_DATA_HOOK,
renderProps.components,
getDefaultParams(store, renderProps)
);
}
trigger(
UPDATE_HEADER_HOOK,
renderProps.components,
getDefaultParams(store, renderProps)
);
} else {
redirectTo("/404.html");
}
});
history.listen(callback);
callback(history.getCurrentLocation());
};
๊ทธ ํ ํ ์คํธ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ ์ ์์ต๋๋ค.
describe("# match route", () => {
it("should navigate to error page", () => {
fetchData.clientFetchData(history, components, store);
reactRouter.match.mock.calls[0][1](true);
expect(window.location.href).toEqual(`${SERVER_URL}/500.html`);
});
it("should redirect to /hello-world.html page", () => {
fetchData.clientFetchData(history, components, store);
reactRouter.match.mock.calls[0][1](undefined, {
pathname: "/hello-world.html",
search: ""
});
expect(window.location.href).toEqual(`${SERVER_URL}/hello-world.html`);
});
...
๋๋ ํจ๊ณผ๊ฐ ์๋ ์ด ์ผ์ ๋๋ด์๋ค.
global.window = new jsdom.JSDOM('', {
url: 'http://www.test.com/test?foo=1&bar=2&fizz=3'
}).window;
JSDOM ์ค์ ํ์ผ ์๋จ์ ๋ค์์ด ์์ต๋๋ค.
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
url: "http://test.com"
});
const { window } = jsdom;
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
global.document = window.document;
global.window = window;
global.navigator = {
userAgent: 'node.js',
};
global.HTMLElement = window.HTMLElement;
Jest ๊ตฌ์ฑ์์ "testURL": " http://localhost/ "๋ฅผ ์ค์ ํ์ฌ ์์ ํ์ต๋๋ค(์ต์ ๋ฒ์ ์ ์ฌ์ฉ ์ค์ ๋๋ค). ๊ธฐ๋ณธ์ ์ผ๋ก " about:blank "์ด๊ณ JSDOM ์ค๋ฅ๋ฅผ ๋ฐ์์์ผฐ์ต๋๋ค("about:blank" URL์ ๋ค๋ฅธ ๊ฒ์ผ๋ก ๋ณ๊ฒฝํ ์ ์์).
์์:
http://jestjs.io/docs/en/configuration#testurl -๋ฌธ์์ด
https://github.com/jsdom/jsdom/issues/1372
์ด ๊ฒ์๋ฌผ์ด ๋งค์ฐ ์ ์ฉํ๋ค๋ ๊ฒ์ ์์์ต๋๋ค: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking
"Jest ๊ตฌ์ฑ์์ ๋ค์์ ์ค์ ํด์ผ ํฉ๋๋ค.
"testURL": "https://www.somthing.com/test.html"
๊ทธ๋ฐ ๋ค์ ํ
์คํธ๋ฅผ ์ํ beforeEach() ์น์
์์ ๋ค์์ ์ฌ์ฉํ์ฌ ํ์์ ๋ฐ๋ผ ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
history.pushState().
window.history.pushState({}, 'Test Title', '/test.html?query=true');
์ง์! ์ด์ ์์์ ์ธ๊ธํ ์ค๋ ๋์์ ๋ค๋ฅธ ์ฌ๋๋ค์ด ์ ์ํ ๊ฒ์ฒ๋ผ jsdom ๊ตฌ์ฑ์ ์ฌ์ ์ํ ํ์ ์์ด ๋ชจ๋ ํ ์คํธ์ ๋ํ ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ์ด๋ ์ค๋ ๋์์ ์ด ์๋ฃจ์ ์ ์ฐพ์๋์ง ํ์คํ์ง ์์ง๋ง ๊ฒ์ํ ๊ฐ๋ฐ์์๊ฒ ๊ฐ์ฌ๋ฅผ ํํฉ๋๋ค!"
@Mike-Tran ๋น์ ์ ๋ฝ! ๊ทธ๊ฒ์ ์์ ํ ํจ๊ณผ๊ฐ ์์๊ณ ๋งค์ฐ ๊ฐ๋จํ์ต๋๋ค. testURL ์ค์ ์ ์ฌ์ฉํ ํ์์กฐ์ฐจ ์์์ต๋๋ค.
@Mike-Tran ์๋ํฉ๋๋ค! ๊ฐ์ฌ ํด์! ๊ทธ๋ฌ๋ testURL
๋๋ beforeEach
ํ์ํ์ง ์์์ต๋๋ค. ๋ ๊ทธ๋ฅํ๋ค:
window.history.pushState({}, 'Test Title', '/test.html?query=true');
๊ทธ๋ฆฌ๊ณ ์ด์ ๋ ์ด์ Object.defineProperty
๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ต๋๋ค ๐
@jcmcneal ๊ฐ์ฌํฉ๋๋ค! (๋๋ด 23.0.0)
๋ชฉํ๊ฐ window
๊ฐ์ฒด๋ฅผ ์กฐ๋กฑํ๋ ๊ฒ์ด๋ผ๋ฉด ๋ค์์ ๋ด (๊ทธ๋ ๊ฒ ์ฐ์ํ์ง๋ ์์ง๋ง ์๋ํ๋) ์๋ฃจ์
์
๋๋ค.
์ธํฐํ์ด์ค(์ธํฐํ์ด์ค๊ฐ ์ฌ๋ฐ๋ฅธ ๋จ์ด์ธ์ง ํ์คํ์ง ์์ง๋ง ์์ ์ ์ดํดํ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค) ํด๋์ค๋ฅผ ๋ง๋ญ๋๋ค.
// window.js
export const { location } = window;
์ค์ ์ฝ๋์์ window
๋ฅผ ์ธํฐํ์ด์ค ๋ฉ์๋๋ก ๋ฐ๊พธ์ญ์์ค(์: win
).
// myFile.js
import * as win from './window';
export function doSomethingThatRedirectsPage() {
win.location.href = 'google.com';
}
๊ทธ๋ฐ ๋ค์ ๋๋ด ํ ์คํธ์์ jsdom์ด ๋ถํํ์ง ์๋๋ก ์กฐ๋กฑํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ฃผ์ฅํ ์๋ ์์ต๋๋ค.
// myFile.test.js
import * as myFile from './myFile';
import * as win from './window';
it('should redirect', () => {
win.location = { href: 'original-url' };
expect(win.location.href).toBe('original-url');
myFile.doSomethingThatRedirectsPage();
expect(win.location.href).toBe('google.com');
});
@Mike-Tran, @jcmcneal ๊ฐ์ฌํฉ๋๋ค! ๋ชจ๋ ๊ฒ์ ์ธก๋ฉด์ผ๋ก ์๋ํฉ๋๋ค!
ํด๋์ค SSOtestComponent๋ React.Component๋ฅผ ํ์ฅํฉ๋๋ค. {
componentDidMount() {
let isSuccess = this.props.location.pathname === '/sso/test/success' ? true : false
window.opener.postMessage({ type: "sso_test", isSuccess,...this.props.location.query}, window.location.origin)
}
onSsoAuthenticate() {
}
componentWillUnmount() {
}
render() {
return (<Loader />);
}
}
module.exports = SSOtestComponent;
๋๋ enjyme์ ์ฌ์ฉํ์ฌ ๋จ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์์ฑํ๊ณ ์กฐ๊ฑด window.location์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋๋ดํฉ๋๋ค. ...pls๋ ๋ต๋ณ์ ์ ๊ณตํฉ๋๋ค
์ด๊ฒ์ ๋๋ฅผ ์ํด ์ผํ์ต๋๋ค
const location = JSON.stringify(window.location);
delete window.location;
Object.defineProperty(window, 'location', {
value: JSON.parse(location)
});
Object.defineProperty(global.location, 'href', {
value: 'http://localhost/newURL',
configurable: true
});
jest ๋ฒ์ 23.6.0์์
์ด๊ฒ์ ๋๋ฅผ ์ํด ์ผํ์ต๋๋ค.
delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }
@FelipeBohnertPaetzold ๊ฐ์ฌํฉ๋๋ค
@FelipeBohnertPaetzold ๊ฐ์ฌํฉ๋๋ค. ๋ด ์ฝ๋์์ location.host
๋ฅผ ์ฌ์ฉํ๊ณ ์์๊ธฐ ๋๋ฌธ์ ์ ์ฒด ์์น ๊ฐ์ฒด๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ ์์น ์์ฑ์ ์๋์ผ๋ก ์ ๋ฌํ๋ ๊ฒ๋ณด๋ค ๋ค์์ด ๋ ํจ๊ณผ์ ์ด์์ต๋๋ค.
delete global.window.location;
global.window.location = new URL("https://www.ediblecode.com/");
์ฐธ๊ณ ๋ก ์ด๊ฒ์ Node 6.13+( URL ํด๋์ค ๋ฌธ์ ์ฐธ์กฐ)์์ ์๋ํ๋ฉฐ ์ ๋ Jest 24๋ฅผ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค.
๋ํ ์ด๊ฒ์ ์๋ URL์์๋ ์๋ํ์ง ์์ต๋๋ค. https://url.spec.whatwg.org/#example -url-parsing์ ์ฐธ์กฐํ์ธ์.
์ด TypeScript ๋ Jest 24.0.0 ๋ฐ Node 10.15.0 ์์ ์๋ํฉ๋๋ค.
import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');
describe('setup tests', () => {
describe('window.location', () => {
const saveLocation = window.location;
afterAll(() => {
delete window.location;
window.location = saveLocation;
});
it('location.assign assigns a location', () => {
window.location.assign('http://foo.com');
expect(window.location.href).toBe('http://foo.com/');
(window.location.assign as jest.Mock<void, [string]>).mockClear();
});
it('location.replace replaces a location', () => {
window.location.replace('http://bar.com');
expect(window.location.href).toBe('http://bar.com/');
(window.location.replace as jest.Mock<void, [string]>).mockClear();
});
it('location.reload is a spy', () => {
window.location.reload();
expect(window.location.reload).toHaveBeenCalledTimes(1);
(window.location.reload as jest.Mock).mockClear();
});
});
});
interface MockedLocation extends Location {
assign: jest.Mock<void, [string]>;
reload: jest.Mock;
replace: jest.Mock<void, [string]>;
}
interface MockedWindow extends Window {
location: MockedLocation;
}
export function mockWindow(win: Window = window, href = win.location.href) {
const locationMocks: Partial<MockedLocation> = {
assign: jest.fn().mockImplementation(replaceLocation),
reload: jest.fn(),
replace: jest.fn().mockImplementation(replaceLocation),
};
return replaceLocation(href);
function replaceLocation(url: string) {
delete win.location;
// tslint:disable-next-line:no-any
win.location = Object.assign(new URL(url), locationMocks) as any;
return win as MockedWindow;
}
}
import { mockWindow } from './testUtils';
describe('test utils', () => {
describe('mockWindow', () => {
const saveLocation = window.location;
afterAll(() => {
delete window.location;
window.location = saveLocation;
});
it('location.assign assigns a location', () => {
const { assign } = mockWindow().location;
assign('http://foo.com');
expect(window.location.href).toBe('http://foo.com/');
assign.mockClear();
});
it('location.replace replaces a location', () => {
const { replace } = mockWindow().location;
replace('http://bar.com');
expect(window.location.href).toBe('http://bar.com/');
replace.mockClear();
});
it('location.reload is a spy', () => {
const { reload } = mockWindow().location;
reload();
expect(window.location.reload).toHaveBeenCalledTimes(1);
reload.mockClear();
});
});
});
@jedmao
์ด๋ด, ์น๊ตฌ) ์ข์ ์ ํธ๋ฆฌํฐ!
src/setupTests.test.ts
์ ๋ํ ํ
์คํธ๋ ์ฝ๊ฐ ์ค๋ณต๋๋ฏ๋ก src/testUtils.test.ts์์ mockWindow
util์ ์ด๋ฏธ ์์ ํ ํ
์คํธํ๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ฐ๋ผ์ src/setupTests.ts
์ ๋ํ ํ
์คํธ์์๋ ์ฌ๋ฐ๋ฅธ ์ธ์๋ฅผ ์ฌ์ฉํ์ฌ mockWindow
๋ฅผ ํธ์ถํ๋ ๊ฒ์ผ๋ก ์ถฉ๋ถํฉ๋๋ค.
๊ฐ์ฌํฉ๋๋ค)
@tzvipm @jup-iter ๐ ๊ฐ์ฌํฉ๋๋ค. ๋ฐฉ๊ธ @jedmao/storage
๋ฐ @jedmao/location
๋ฅผ ์ถ์ํ๋๋ฐ ๋ ๋ค Jest์ ๋ํด ์์ ํ ๋ถ๊ฐ์ง๋ก ์ ์
๋๋ค. npm ํจํค์ง๊ฐ ์์ ํ ํ
์คํธ๋๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ ํ
์คํธ๋ฅผ ์์ฑํ์ง ์๊ณ ๋ ์ ์ ํ ๋ฉ์๋๋ฅผ spyOn
ํ ์ ์์ด์ผ ํฉ๋๋ค.
Vue๋ฅผ ์ฌ์ฉํ ๋ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ $ this.$router.go({...})
this.$router.push({...})
๋ฅผ ์ฌ์ฉํ์ธ์.
1ํ์ ์๋ ์ฝ๋๋ฅผ ๋ฐฐ์นํฉ๋๋ค.
delete global.window.location;
global.window.location = "";
window.location์ ๋ณ๊ฒฝํ๋ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ์บก์ฒํ ์ ์์ต๋๋ค.
"๋๋ด": "^23.6.0"
์ด๊ฒ์ ์๋ํฉ๋๋ค:
delete window.location;
window.location = Object.assign({}, window.location);
const url = Object.assign({}, new URL('http://google.com'));
Object.keys(url).forEach(prop => (window.location[prop] = url[prop]));
์๋๋ฉด ๋ ๋์ ์์ง ...
delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;
๋น์ ์ด ๋ง์ต๋๋ค. ์ด๊ฒ์ ์ค์ ๋ก jsdom ๋ฌธ์ ์ ๋๋ค. Facebook์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํํ ์์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Object.defineProperty(window.location, 'href', { writable: true, value: 'some url' });
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ํจ๊ณผ์ ์ด์ง๋ง ๋ด๋ถ์ ์ผ๋ก๋ ์ฌ์ ํ jsdom 7์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
Object.defineProperty
์์ ๋ฐฉ์์ด ๊ด์ฐฎ๋ค๊ณ ์๊ฐํ๋ฏ๋ก ์ด ์์ ์ ๋ซ๊ฒ ์ต๋๋ค. jsdom 8์์ ์๋ํ์ง ์๋๋ค๋ฉด ๊ธฐ๊บผ์ด ๋ค์ ์ด๊ฒ ์ต๋๋ค.
์, location.search
๋ฐ location.hash
์ ์ฒ๋ฆฌํ๋ ๋ช ๊ฐ์ง ํจ์๊ฐ ์์ผ๋ฉฐ ์ธ๊ธํ ๋๋ก defineProperty
๋ก ํ
์คํธํ๊ณ ์ถ์ต๋๋ค. ์๋ํ์ง ์์ต๋๋ค!
jest
Error: Not implemented: navigation (except hash changes)
๋ฅผ ์ฐพ์์ต๋๋ค.
console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
Error: Not implemented: navigation (except hash changes)
at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>) undefined
์ด์ ๋ด ๊ธฐ๋ฅ์ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ํ ๋ชจ๋ฆ ๋๋ค.
๋๊ตฌ๋ ํ
์คํธ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. url
๋น์ ์ด ๋ง์ต๋๋ค. ์ด๊ฒ์ ์ค์ ๋ก jsdom ๋ฌธ์ ์ ๋๋ค. Facebook์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํํ ์์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Object.defineProperty(window.location, 'href', { writable: true, value: 'some url' });
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ํจ๊ณผ์ ์ด์ง๋ง ๋ด๋ถ์ ์ผ๋ก๋ ์ฌ์ ํ jsdom 7์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
Object.defineProperty
์์ ๋ฐฉ์์ด ๊ด์ฐฎ๋ค๊ณ ์๊ฐํ๋ฏ๋ก ์ด ์์ ์ ๋ซ๊ฒ ์ต๋๋ค. jsdom 8์์ ์๋ํ์ง ์๋๋ค๋ฉด ๊ธฐ๊บผ์ด ๋ค์ ์ด๊ฒ ์ต๋๋ค.์,
location.search
๋ฐlocation.hash
์ ์ฒ๋ฆฌํ๋ ๋ช ๊ฐ์ง ํจ์๊ฐ ์์ผ๋ฉฐ ์ธ๊ธํ ๋๋กdefineProperty
๋ก ํ ์คํธํ๊ณ ์ถ์ต๋๋ค. ์๋ํ์ง ์์ต๋๋ค!
jest
Error: Not implemented: navigation (except hash changes)
๋ฅผ ์ฐพ์์ต๋๋ค.console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29 Error: Not implemented: navigation (except hash changes) at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17) at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3) at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3) at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5) at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10) at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25) at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14) at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37) at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12) at new Promise (<anonymous>) undefined
์ด์ ๋ด ๊ธฐ๋ฅ์ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ํ ๋ชจ๋ฆ ๋๋ค.
๋๊ตฌ๋ ํ ์คํธ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
url
๊ทธ๋ฆฌ๊ณ ์ ์ํฉ์์๋ jest.config.js
ํ์ผ์ testURL
ํ๋๊ฐ ์๋ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ ํ
์คํธ ์ ์ testURL์ ๋ณ๊ฒฝํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
์ด ๊ฒ์๋ฌผ์ด ๋งค์ฐ ์ ์ฉํ๋ค๋ ๊ฒ์ ์์์ต๋๋ค: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking
"Jest ๊ตฌ์ฑ์์ ๋ค์์ ์ค์ ํด์ผ ํฉ๋๋ค.
"testURL": "https://www.somthing.com/test.html"
๊ทธ๋ฐ ๋ค์ ํ ์คํธ๋ฅผ ์ํ beforeEach() ์น์ ์์ ๋ค์์ ์ฌ์ฉํ์ฌ ํ์์ ๋ฐ๋ผ ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
history.pushState().window.history.pushState({}, 'Test Title', '/test.html?query=true');
์ง์! ์ด์ ์์์ ์ธ๊ธํ ์ค๋ ๋์์ ๋ค๋ฅธ ์ฌ๋๋ค์ด ์ ์ํ ๊ฒ์ฒ๋ผ jsdom ๊ตฌ์ฑ์ ์ฌ์ ์ํ ํ์ ์์ด ๋ชจ๋ ํ ์คํธ์ ๋ํ ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ์ด๋ ์ค๋ ๋์์ ์ด ์๋ฃจ์ ์ ์ฐพ์๋์ง ํ์คํ์ง ์์ง๋ง ๊ฒ์ํ ๊ฐ๋ฐ์์๊ฒ ๊ฐ์ฌ๋ฅผ ํํฉ๋๋ค!"
ํ์ํ ์๋ฃจ์
!!! ๋งค์ฐ ๊ฐ์ฌํฉ๋๋ค! @๋ง์ดํฌํธ๋
๋๋ ์ด์ ๊ฐ์ด ์งง๊ณ ์นจ์ต์ ์ด์ง ์์ ์๋ฃจ์
์ ์ํ์ต๋๋ค!
2019๋ 6์๋ถํฐ ์ด ์์ ์ ์ํํ๋ ค๋ฉด ๋ค์์ ์ํํด์ผ ํ์ต๋๋ค.
delete global.window.location;
global.window = Object.create(window);
global.window.location = {
port: '123',
protocol: 'http:',
hostname: 'localhost',
};
๋๋ ์ด๊ฒ์ ์ฌ์ฉํ๋ค....
window.history.pushState({}, '', `${url}/`);
์๋ง๋ ๋ด JSDOMTestWrapper์ ์ผ๋ถ๊ฐ ๋๊ตฐ๊ฐ๋ฅผ ๋์ธ ์ ์์ต๋๋ค.
/** <strong i="6">@type</strong> {Window} */
this.testWindowObject = Object.create(window);
const wnd = this.testWindowObject;
this.testWindowObject.history = {
state: null,
prev: { /** <strong i="7">@todo</strong> immutable stack with the go(step) method emulation */
state: null,
pathname: null,
search: null,
},
go(step) {
logger.special('history go called', step);
logger.warn('history go has not supported yet');
},
back() {
this.state = this.prev.state;
wnd.location.pathname = this.prev.pathname;
wnd.location.search = this.prev.search;
const eventData = this.state ? { url: this.state.displayURL, newState: this.state, type: 'push' } : null;
wnd.sm.eventsService.triggerEvent(ROUTER_EVENTS.ROUTE_PUSH, eventData);
wnd.sm.urlService.simpleRouteTo(`${ wnd.location.pathname || '' }${ wnd.location.search || '' }`);
logger.special('history back emulated');
},
pushState(state, title, url) {
this.prev.state = Object.assign({}, this.state);
this.prev.pathname = '' + wnd.location.pathname;
this.prev.search = '' + wnd.location.search;
this.state = state;
if (title) wnd.document.title = title;
const [p, s] = url.split('?');
wnd.location.pathname = p;
wnd.location.search = s ? `?${ s }` : '';
logger.special('push state emulated', { state, title, url });
},
replaceState(state, title, url) {
this.prev.state = Object.assign({}, this.state);
this.prev.pathname = '' + wnd.location.pathname;
this.prev.search = '' + wnd.location.search;
this.state = state;
if (title) wnd.document.title = title;
const [p, s] = url.split('?');
wnd.location.pathname = p;
wnd.location.search = s ? `?${ s }` : '';
logger.special('replace state emulated', { state, title, url });
logger.special('test: urlService.getPathName()', wnd.sm.urlService.getPathName());
},
};
this.testWindowObject.innerWidth = WND_WIDTH;
this.testWindowObject.innerHeight = WND_HEIGHT;
this.testWindowObject.fetch = fetchFn;
this.testWindowObject.localStorage = lstMock;
this.testWindowObject.scrollTo = (x, y) => {
/** not implemented yet https://github.com/jsdom/jsdom/issues/1422 */
if (typeof x !== 'number' && (x.left || x.top)) {
y = x.top;
x = x.left;
}
// logger.info(`window.scrollTo(${ x }, ${ y })`);
};
if (fetchFn === JSDOMTestWrapper.FETCH_FN.DEV_MOCK) {
global.Request = RequestMock;
this.testWindowObject.Request = RequestMock;
}
if (href) {
this.testWindowObject.location = Object.assign({}, this.testWindowObject.location, urlapi.parse(href));
}
else {
this.testWindowObject.location = Object.assign({}, this.testWindowObject.location);
}
(function(ELEMENT) {
ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
ELEMENT.closest = ELEMENT.closest || function closest(selector) {
if (!this) return null;
if (this.matches(selector)) return this;
if (!this.parentElement) {return null}
else return this.parentElement.closest(selector)
};
ELEMENT.getBoundingClientRect = ELEMENT.getBoundingClientRect || (() =>
({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 }));
}(Element.prototype));
this.testWindowObject.getBoundingClientRect = () =>
({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 });
this.testWindowObject.__resizeListeners__ = [];
this.testWindowObject.__resizeTriggers__ = {};
this.testWindowObject._detectElementResize = {
removeResizeListener: () => {},
};
this.testWindowObject.matchMedia = jest.fn().mockImplementation(query => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
this.rftpr = () => {};
this.mode = mode;
this.renderFirstTimePromise = new Promise((resolve) => {
this.rftpr = resolve;
});
this.marpr = () => {};
this.mobileAppReadyPromise = new Promise((resolve) => {
this.marpr = resolve;
});
if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
this.testWindowObject.navigator = Object.assign({}, this.testWindowObject.navigator, {
language: storeKey,
appVersion: '5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
vendor: 'Google Inc.',
});
global.intercom = {
registerUnidentifiedUser: jest.fn(),
registerForPush: jest.fn(),
};
}
const XApp = mode ? MobileApp : App;
const app = <XApp window={ this.testWindowObject } rftResolve={ this.rftpr } storeKey={ storeKey } apiHost={ apiVersion } forceMobileDetection={ mode } />;
render(app, this.testWindowObject.document.body);
if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
setTimeout(() => {
this.testWindowObject.sm.deviceService.appRestorePathHasInit = this.marpr;
this.testWindowObject.sm.deviceService.fireEvent(this.testWindowObject.document, 'deviceready');
}, 200);
}
์ด ์ ๊ทผ ๋ฐฉ์์ 2019๋ 9์ 27์ผ๋ถํฐ ์๋ํฉ๋๋ค. https://stackoverflow.com/a/54034379/1344144
global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
value: {
href: url
},
writable: true
});
๋ค๋ฅธ ์๋ฃจ์
์ ํ์ฌ jsdom
์์ฑํ์ง ์๊ณ ์ ์๊ฒ ํจ๊ณผ์ ์
๋๋ค.
jest.config.js
testURL
๋ฅผ ์ค์ ํ๋์ง ํ์ธํ์ญ์์ค.// jest.config.js
'testURL': 'https://someurl.com'
ํ ์คํธ ํ์ผ์์:
window.history.pushState({}, 'Mocked page title', 'www.yoururl.com');
https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking์์ ๋ฐฐ์ ์ต๋๋ค. ๋ผ์ด์ธ์๊ฒ ๊ฐ์ฌํฉ๋๋ค!
๋๋ฅผ ์ํด ์๋ํ์ง ์์ต๋๋ค:
TypeError: ์๊ฒฉ ๋ชจ๋์์๋ ์ฝ๊ธฐ ์ ์ฉ ์์ฑ์ ํ ๋นํ ์ ์์ต๋๋ค.
it("should save hash when history is not found", () => {
const historyBkp = global.window.history;
delete global.window.history;
global.window.history = false;
externalLoader.savePageURL(urlTraining);
expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);
global.window.history = historyBkp;
window.location.hash = "";
});
๋๋ฅผ ์ํด ์๋ํ์ง ์์ต๋๋ค:
TypeError: ์๊ฒฉ ๋ชจ๋์์๋ ์ฝ๊ธฐ ์ ์ฉ ์์ฑ์ ํ ๋นํ ์ ์์ต๋๋ค.
it("should save hash when history is not found", () => { const historyBkp = global.window.history; delete global.window.history; global.window.history = false; externalLoader.savePageURL(urlTraining); expect(window.location.hash).to.be.equal(`#page=${urlTraining}`); global.window.history = historyBkp; window.location.hash = ""; });
์ด๊ฒ์ ์ ์ญ ํ์ผ์ ์ถ๊ฐํ์ญ์์ค.
global.window.location ์ญ์ ;
์ ์ญ.์ฐฝ.์์น = "";
์ด ์ ๊ทผ ๋ฐฉ์์ 2019๋ 9์ 27์ผ๋ถํฐ ์๋ํฉ๋๋ค. https://stackoverflow.com/a/54034379/1344144
global.window = Object.create(window); const url = "http://dummy.com"; Object.defineProperty(window, "location", { value: { href: url }, writable: true });
location.assign์ ์ฌ์ฉํ์ฌ ๋น์ทํ ๊ฒ์ ์๋ํ๊ณ ์์ง๋ง ๋ ์ด์ ์๋ํ์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ jest 24.9.0์์ ๋๋ฅผ ์ํด ์๋ํฉ๋๋ค.
window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');
์ด๊ฒ์ jest 24.9.0์์ ๋๋ฅผ ์ํด ์๋ํฉ๋๋ค.
window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');
ํ๋ผ๋ฏธ์ค ๋ด์์ ์ฝ๋๋ฅผ ์คํํ๊ณ ์์๊ธฐ ๋๋ฌธ์ ์ด ์์ ์ ์ํํ๋ ค๋ฉด ์ฝ๋๋ฅผ ๋น๋๊ธฐ์์ผ๋ก ๋ง๋ค์ด์ผ ํ์ต๋๋ค.
๊ทธ๋์ ์ง๊ธ ์ผํ๊ณ ์์ต๋๋ค ๐
vuex ์์ ์์ chenge ์์น๋ฅผ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ ๋๊น?
async setForm({ rootState, state, commit, dispatch }, formData) {
....
switch (answ.result.type) {
....
case 'redirect':
console.log(answ.data.url);
window.location = answ.data.url;
console.log({ location: window.location.href });
break;
default:
break;
it('setForm - success, redirect', async done => {
expect(window.location.href).toBe('https://www.google.ru/');
์ค๋ฅ๊ฐ ์์ต๋๋ค.
expect(received).toBe(expected) // Object.is equality
Expected: "https://www.google.ru/"
Received: "http://localhost/"
์ด๊ฒ์ ๋๋ฅผ ์ํด ์ผํ์ต๋๋ค
const location = JSON.stringify(window.location); delete window.location; Object.defineProperty(window, 'location', { value: JSON.parse(location) }); Object.defineProperty(global.location, 'href', { value: 'http://localhost/newURL', configurable: true });
jest ๋ฒ์ 23.6.0์์
๊ธ๋ก๋ฒ์ด ๋ญ์ผ?
์ ์ญ ์ ์๋ ์ด๋์ ์์ต๋๊น?
์ด๊ฒ์ ๋๋ฅผ ์ํด ์ผํ์ต๋๋ค.
delete global.window.location global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }
์ด๊ฒ์ ๋ชจ๋ ์๋ ๊ธฐ๋ฅ์ ๊ฐ์ง ์์น๋ฅผ ์์ฑํ์ง๋ง ์กฐ๋กฑํ ์ ์์ต๋๋ค.
beforeAll(() => {
const location = window.location
delete global.window.location
global.window.location = Object.assign({}, location)
})
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๋น์ ์ด ๋ง์ต๋๋ค. ์ด๊ฒ์ ์ค์ ๋ก jsdom ๋ฌธ์ ์ ๋๋ค. Facebook์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํํ ์์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ํจ๊ณผ์ ์ด์ง๋ง ๋ด๋ถ์ ์ผ๋ก๋ ์ฌ์ ํ jsdom 7์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
Object.defineProperty
์์ ๋ฐฉ์์ด ๊ด์ฐฎ๋ค๊ณ ์๊ฐํ๋ฏ๋ก ์ด ์์ ์ ๋ซ๊ฒ ์ต๋๋ค. jsdom 8์์ ์๋ํ์ง ์๋๋ค๋ฉด ๊ธฐ๊บผ์ด ๋ค์ ์ด๊ฒ ์ต๋๋ค.