Jest: window.location.href๋Š” ํ…Œ์ŠคํŠธ์—์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2016๋…„ 04์›” 13์ผ  ยท  70์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: facebook/jest

์•ˆ๋…•ํ•˜์„ธ์š” @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์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ธฐ๊บผ์ด ๋‹ค์‹œ ์—ด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  70 ๋Œ“๊ธ€

๋‹น์‹ ์ด ๋งž์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์‹ค์ œ๋กœ 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 ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

src/setupTests.ts

import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');

src/setupTests.test.ts

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();
        });
    });
});

src/testUtils.ts

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;
    }
}

src/testUtils.test.ts

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({...}) ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

image

1ํ–‰์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
delete global.window.location;
global.window.location = "";

window.location์„ ๋ณ€๊ฒฝํ•˜๋Š” ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์บก์ฒ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"๋†๋‹ด": "^23.6.0"
v10.15.0
6.5.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 ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

  1. ๊ฐ’์— ๊ด€๊ณ„์—†์ด 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: ์—„๊ฒฉ ๋ชจ๋“œ์—์„œ๋Š” ์ฝ๊ธฐ ์ „์šฉ ์†์„ฑ์— ํ• ๋‹นํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

image

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: ์—„๊ฒฉ ๋ชจ๋“œ์—์„œ๋Š” ์ฝ๊ธฐ ์ „์šฉ ์†์„ฑ์— ํ• ๋‹นํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

image

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)
})
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰