Jest: window.location.href não pode ser alterado em testes.

Criado em 13 abr. 2016  ·  70Comentários  ·  Fonte: facebook/jest

Olá @cpojer ,

Este é realmente mais um problema jsdom@8 ...veja tmpvar/jsdom#1388, mas eu quero fixar aqui também, então Jest pega qualquer solução que o jsdom apresente.

Anteriormente com [email protected]/[email protected] você poderia escrever um teste assim:

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

E esse teste passaria. Desde jsdom@8 isso não é mais possível e esses testes falham.

Parece que o jsdom está procurando algum tipo de recurso, só queria ter certeza de que o Jest pegará esse recurso quando estiver disponível.

Comentários muito úteis

Você está certo, este é realmente um problema de jsdom. No Facebook, o que fizemos para contornar isso foi usar isso:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

isso funciona para nós, no entanto, ainda estamos no jsdom 7 internamente.

Vou fechar isso, pois acredito que a maneira Object.defineProperty de fazer as coisas é boa. Se isso não funcionar para você no jsdom 8, ficarei feliz em reabri-lo.

Todos 70 comentários

Você está certo, este é realmente um problema de jsdom. No Facebook, o que fizemos para contornar isso foi usar isso:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

isso funciona para nós, no entanto, ainda estamos no jsdom 7 internamente.

Vou fechar isso, pois acredito que a maneira Object.defineProperty de fazer as coisas é boa. Se isso não funcionar para você no jsdom 8, ficarei feliz em reabri-lo.

Legal, obrigado vou tentar isso.

@cpojer , não consigo descobrir o que preciso clicar para reabrir este problema ...

Existe alguma maneira no ambiente jest para chamar jsdom.changeUrl(window, url) conforme descrito aqui https://github.com/tmpvar/jsdom#change -the-url-of-an-existing-jsdom- window-instance em [email protected] ?

ticket antigo, mas para aqueles que ainda têm esse problema, começamos a usar window.location.assign() para que em nossos testes possamos simular a função assign assim ..

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

Obrigado @th3fallen . Que legal!

Aliás @cpojer eu começo no FB em 1º de maio... ;P

Agradável!

Estou tentando migrar nossos testes de Mocha+Chai+Sinon.js para Jest e não consigo descobrir como mudar de local para um teste específico.
Jest 19.x usa JSDom 9.12 que não permite mudar de localização usando Object.defineProperty trick. Além disso, não posso usar jsdom.changeURL() pelos motivos descritos em tmpvar/jsdom#1700.
@cpojer que tal implementar algum método de proxy para jsdom.changeURL() no Jest?

@okovpashko estamos planejando expor o jsdom ao ambiente: https://github.com/facebook/jest/issues/2460

Object.defineProperty funciona para nós no FB.

@thymikee Eu vi esse problema, mas pensei que a proposta foi rejeitada.
@cpojer Eu interpretei mal o seu exemplo e o misturei com outros relacionados a esse problema, onde as pessoas sugeriram usar Object.defineProperty(window, 'location', {value: 'url'}); . Obrigada!

Eu preciso mudar não apenas o href, então escrevi um método simples, que pode ser útil para alguém que vá ler este tópico:

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

Desculpas por arrastar ainda mais este tópico, mas tentei zombar da função push conforme sugerido ...

reactRouterReduxMock.push = (url) => {
   Object.defineProperty(window.location, 'href', {
    writable: true,
    value: url
       })
})

mas ainda estou recebendo um erro de jsdom que não consigo contornar:

       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

Eu percebo que isso é um erro de jsdom, mas para aqueles que resolveram isso, existe mais algum contexto de configuração que você possa compartilhar que possa me permitir contornar isso?

Obrigado

@matt-dalton tente minha sugestão em https://github.com/facebook/jest/issues/890#issuecomment -295939071 funciona bem para mim

@matt-dalton qual é o seu URL? você tem testURL definido em seu jest-config.json ou inicializa como about:blank ?

@ianlyons Sim, eu configurei o valor de "https://test.com/" para isso no package.json, e nenhum dos caminhos está aparecendo como blank .

@th3fallen Se entendi corretamente, acho que isso não funciona para o meu caso de uso. Você está passando o URL como um valor de contexto que faz com que a atribuição seja acionada? Estou tentando montar um teste de integração rudimentar, então quero verificar como o roteador responde à carga de dados inicial. Eu zombei da resposta da API e, em seguida, preciso que a alteração da URL ocorra usando a lógica do aplicativo (ou seja, não quero acioná-la externamente).

Object.defineProperty parece fazer o truque para testar a funcionalidade que depende de window.location.search , por exemplo. Dito isto, altera window.location.search para que outros testes possam ser afetados. Existe uma maneira de "desfazer" as alterações que você fez em window.location.search via Object.defineProperty , como as funções de simulação de jest têm a função mockReset ?

@msholty-fd você pode tentar esta abordagem:

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

Parou de funcionar no Jest 22.0.1

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

Mensagem de erro:

TypeError: Cannot redefine property: href
        at Function.defineProperty (<anonymous>)

Hmm, podemos precisar de alguma forma permitir que as pessoas chamem reconfigure . https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring -the-jsdom-with-reconfiguresettings

Abriu um novo problema relacionado a isso, já que este foi fechado: #5124

@SimenB Não estou convencido de que Jest deva consertar isso. O JSDOM deve permitir que window.location.assign() funcione como pretendido e reconfigure a saída de window.location.href etc.

Eu tenho TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL porque jsdom tem url base padrão para about:blank

Tentei atribuir uma url base para jsdom , passei 4 horas nela sem sucesso (sei como fazer, basta inserir <base href='your_base_url' /> no dom; mas , o dom é criado por jest , não por mim, então desisti.

a solução Object.defineProperty só funciona com a versão antiga de jsdom (você obtém um erro 'não é possível redefinir a propriedade com a versão posterior de jsdom );
se você estiver usando jsdom ver > 10, como @th3fallen mencionou é a solução certa.
usar window.location.assign é o caminho certo a seguir

Se você quiser apenas algum outro URL além de about:blank , você pode usar testURL config.

obrigado @SimenB pela sua resposta.

Não, eu estava falando de base url não url . Eu tenho um código que fará window.location.href="/login" e ao executar jest , jsdom lançar exceção reclamando que /login não é uma url válida

TypeError: Could not parse "/login" as a URL

Verifiquei o código-fonte de jsdom e percebi que isso ocorre porque não tenho uma configuração de URL base (isso é equivalente a digitar "/login" na barra de URL do navegador sem um endereço base).

com jsdom , normalmente podemos configurar o URL base via

global.jsdom = new JSDOM('<html><head> <base href="base_url" /></head></html>')

mas porque jest configurou jsdom , está além do nosso controle.
--- atualização: suponho que posso adicionar explicitamente jsdom como dependência e configurar jsdom manualmente.

Eu então encontrei uma solução que é substituir window.location.href= por window.location.assign e simular a função assign e funcionou para mim

@bochen2014 este problema tem mais informações sobre como usar a versão mais recente do jsdom: #5124

tl;dr: você pode simular window.location.assign() , ou pode usar o jest-environment-jsdom-global , que permitirá reconfigurar o jsdom em execução.

obrigado @simon360

Isso é o que eu fiz ;-)
Usei jsdom.reconfigure para configurar diferentes urls iniciais em meus testes, e sempre que preciso alterar url no código (não teste), uso window.location.assign e zombei dele. que funcionou para mim.

apenas para pessoas que podem / terão o mesmo problema, para definir o URL do seu jsdom

// jest.config.js
 module.exorts={ 
  testURL: 'http://localhost:3000',
  // or : 
  testEnvironmentOptions: {
     url: "http://localhost:3000/",
    referrer: "https://example.com/",
  }
}

observe que isso definirá a url para todos os seus testes;
se você quiser uma url diferente em alguns testes específicos, use jsdom.reconfigure api;
se você precisar alterar o URL rapidamente fora do código de teste de unidade (ou seja, código de produção), você precisará usar window.location.assign e zombar dele.

Postei em outro ticket, mas vou postar aqui:

Encontrou uma boa solução para o Jest 21.2.1

Ok, até agora a solução mais fácil para isso é:
Entre nas configurações do Jest (por exemplo, usarei package.json):

"jest": { "testURL": "http://localhost" }

Agora você terá acesso ao objeto window e poderá definir a URL para o que quiser durante os testes.

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

Espero que isso ajude alguém.

@petar-prog91 isso foi útil. Você tem um erro de digitação - deve ser testURL não TestURL

@BarthesSimpson obrigado pelo aviso, comentário atualizado.

Pare de postar isso, não funciona no jest": "^22.4.2"

Oi,
Eu usei isso no teste, excluo o estado global e crio um novo com 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 confirma — dá TypeError: Cannot redefine property: href

@annemarie35 não funciona — ReferenceError: JSDOM is not defined

Não sei se isso ajudaria alguém, mas é o que estou fazendo atualmente.

const redirectTo = (url: string): void => {
  if (process.env.NODE_ENV === "test") {
    global.jsdom.reconfigure({ url: `${getBaseUrl()}${url}` });
  } else {
    window.location.replace(url);
  }
};

Escreva uma função de redirecionamento e use-a. Portanto, ao testar o env, ele dependerá do jsdom.reconfigure url para alterar a parte do URL.

eu uso assim

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

Depois disso, no seu teste, pode ser assim

    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`);
      });
...

Acabei fazendo isso que funcionou:

global.window = new jsdom.JSDOM('', {
  url: 'http://www.test.com/test?foo=1&bar=2&fizz=3'
}).window;

Eu tenho isso no topo do meu arquivo de configuração 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;

Corrigido definindo "testURL": " http://localhost/ " na configuração do Jest (estou usando a versão mais recente). Por padrão, é " about:blank " e estava causando o erro JSDOM (você não pode alterar o URL "about:blank" para outra coisa).

Recursos:
http://jestjs.io/docs/en/configuration#testurl -string
https://github.com/jsdom/jsdom/issues/1372

Achei este post muito útil: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Na sua configuração do Jest, certifique-se de definir o seguinte:

"testURL": "https://www.somthing.com/test.html"

Em seguida, na seção beforeEach() para seu teste, altere o caminho conforme necessário usando
history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voilá! Agora você altera seu caminho para qualquer teste, sem precisar substituir nenhuma configuração do jsdom, como outros sugerem no tópico mencionado acima. Não tenho certeza em qual tópico encontrei esta solução, mas parabéns ao dev que a postou!"

@Mike-Tran Você arrasa! Isso funcionou totalmente, tão simples. Eu nem precisei usar a configuração testURL.

@Mike-Tran Isso funciona! Te agradece! No entanto, eu não precisava do testURL ou beforeEach . Eu apenas fiz:

window.history.pushState({}, 'Test Title', '/test.html?query=true');

E agora não preciso mais usar Object.defineProperty 😅

@jcmcneal obrigado que fez isso por mim! (jogo 23.0.0)

Se seu objetivo é zombar do objeto window , aqui está minha solução (não tão elegante, mas funciona):

Crie uma classe de interface (não tenho certeza se interface é a palavra certa, mas espero que você entenda):

// window.js
export const { location } = window;

No seu código atual, troque window pelos métodos da interface, por exemplo, win

// myFile.js
import * as win from './window';

export function doSomethingThatRedirectsPage() {
  win.location.href = 'google.com';
}

Então, em seus testes de brincadeira, você apenas zomba deles para que o jsdom não reclame. Você pode até afirmá-los:

// 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 obrigado! Tudo funciona como aspectado!

class SSOtestComponent estende 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;

estou escrevendo o caso de teste de unidade usando enjyme e jest como escrever a condição window.location ... por favor, dê a resposta

isso funcionou para mim

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

no jest versão 23.6.0

Isso funcionou para mim.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

@FelipeBohnertPaetzold obrigado

Obrigado @FelipeBohnertPaetzold. Eu estava usando location.host no meu código, então descobri que precisava de um objeto de localização completo , então o seguinte funcionou melhor para mim, em vez de ter que passar manualmente cada propriedade de localização:

delete global.window.location;
global.window.location = new URL("https://www.ediblecode.com/");

Observe que isso funciona no Node 6.13+ (consulte URL class docs ) e eu estava usando o Jest 24.

Observe também que isso não funciona com URLs relativos, consulte https://url.spec.whatwg.org/#example -url-parsing.

Este TypeScript está funcionando para mim no Jest 24.0.0 e 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

Ei, cara) Ótimo util!

Para mim testes em src/setupTests.test.ts um pouco redundantes, pois você já testou completamente mockWindow util em src/testUtils.test.ts. Então, em testes para src/setupTests.ts basta testar, que você chame mockWindow com argumentos corretos.

Obrigada)

@tzvipm @jup-iter obrigado pelo 👍. Acabei de lançar @jedmao/storage e @jedmao/location , que são completamente independentes do Jest. Você deve ser capaz de spyOn os métodos apropriados sem escrever nenhum teste adicional, pois os pacotes npm vêm completamente testados.

Caso você esteja recebendo este erro ao usar o Vue, basta usar this.$router.push({...}) em vez de this.$router.go({...})

image

Coloque o código abaixo na linha 1:
delete global.window.location;
global.window.location = "";

Um evento de clique que está alterando o window.location agora pode ser capturado.

"brincadeira": "^23.6.0"
v10.15.0
6.5.0

Isso funciona:

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]));

Ou melhor ainda...

delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;

Você está certo, este é realmente um problema de jsdom. No Facebook, o que fizemos para contornar isso foi usar isso:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

isso funciona para nós, no entanto, ainda estamos no jsdom 7 internamente.

Vou fechar isso, pois acredito que a maneira Object.defineProperty de fazer as coisas é boa. Se isso não funcionar para você no jsdom 8, ficarei feliz em reabri-lo.

Sim, eu tenho algumas funções lidando com location.search e location.hash , e quero testar com defineProperty como você mencionou. Não vai funcionar!

Quando desativo o modo silencioso jest , encontrei isto: 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

E agora não tenho ideia de como testar minhas funções.

Alguém tem como mudar o teste url

Você está certo, este é realmente um problema de jsdom. No Facebook, o que fizemos para contornar isso foi usar isso:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

isso funciona para nós, no entanto, ainda estamos no jsdom 7 internamente.
Vou fechar isso, pois acredito que a maneira Object.defineProperty de fazer as coisas é boa. Se isso não funcionar para você no jsdom 8, ficarei feliz em reabri-lo.

Sim, eu tenho algumas funções lidando com location.search e location.hash , e quero testar com defineProperty como você mencionou. Não vai funcionar!

Quando desativo o modo silencioso jest , encontrei isto: 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

E agora não tenho ideia de como testar minhas funções.

Alguém tem como mudar o teste url

E na minha situação, ter um campo testURL no arquivo jest.config.js pode funcionar. Mas e se eu quiser mudar testURL antes de cada teste.

Achei este post muito útil: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Na sua configuração do Jest, certifique-se de definir o seguinte:

"testURL": "https://www.somthing.com/test.html"

Em seguida, na seção beforeEach() para seu teste, altere o caminho conforme necessário usando
history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voilá! Agora você altera seu caminho para qualquer teste, sem precisar substituir nenhuma configuração do jsdom, como outros sugerem no tópico mencionado acima. Não tenho certeza em qual tópico encontrei esta solução, mas parabéns ao dev que a postou!"


Excelente solução!!! Muito obrigado! @Mike-Tran
Queria uma solução curta e não invasiva como esta!

Para fazer isso funcionar a partir de junho de 2019, tive que fazer isso:

    delete global.window.location;
    global.window = Object.create(window);
    global.window.location = {
      port: '123',
      protocol: 'http:',
      hostname: 'localhost',
    };

Eu uso isso....

window.history.pushState({}, '', `${url}/`);

Provavelmente parte do meu JSDOMTestWrapper pode ajudar alguém

    /** <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);
    }

Essa abordagem funciona a partir de 27 de setembro de 2019: 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
});

Outra solução funciona para mim atualmente sem escrever jsdom :

  1. Certifique-se de ter definido testURL em jest.config.js não importa o valor:
// jest.config.js
'testURL': 'https://someurl.com'

Em seu arquivo de teste:

window.history.pushState({}, 'Mocked page title', 'www.yoururl.com');

Aprendi em: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Obrigado a Ryan!

não está funcionando para mim:

TypeError: atribuição a propriedades somente leitura não permitidas no modo estrito

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 = "";
});

não está funcionando para mim:

TypeError: atribuição a propriedades somente leitura não permitidas no modo estrito

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 = "";
});

adicione isso ao arquivo global.

excluir global.window.location;
global.window.location = "";

Essa abordagem funciona a partir de 27 de setembro de 2019: 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
});

Estou tentando algo semelhante, com location.assign, mas parece que isso não está mais funcionando.

isso funciona para mim no jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

isso funciona para mim no jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

Eu tive que tornar o código assíncrono para que isso funcionasse porque eu estava executando o código dentro de uma promessa.

então está funcionando agora 😃

Como testar a localização do chenge na ação do vuex?

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/');

tenho erro:

    expect(received).toBe(expected) // Object.is equality

    Expected: "https://www.google.ru/"
    Received: "http://localhost/"

isso funcionou para mim

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

no jest versão 23.6.0

qual é o mundial?

onde está a definição global?

Isso funcionou para mim.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

isso cria um local com toda a funcionalidade original, mas é zombável:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
Esta página foi útil?
0 / 5 - 0 avaliações