Jsdom: Erro: Não implementado: navegação

Criado em 12 jan. 2018  ·  49Comentários  ·  Fonte: jsdom/jsdom

Após a atualização recente jest (que usa jsdom em segundo plano) da versão 21.2.0 para 22.0.6 comecei a receber o erro: "Error: Not implemented:" navigation

Meu código depende de window.location e eu uso nos testes:

beforeEach(() => {
                window.location.href = `/ms/submission/?mybib`;
                window.location.search = '?mybib';
});

Existe uma maneira de definir um valor de window.location.search usando a nova versão do jsdom?

Comentários muito úteis

Permita-me postar a resposta à minha própria pergunta 😁
Eu simplesmente substituo os usos de window.location = url; e window.location.href = url; por

window.location.assign(url);

e então em meus testes eu fiz:

sinon.stub(window.location, 'assign');
expect(window.location.assign).to.have.been.calledWith(url);

Funciona como um encanto - espero que possa ajudar outra pessoa 👍

Todos 49 comentários

Estou recebendo este erro também

Error: Not implemented: navigation (except hash changes)
    at module.exports (...\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
    at navigateFetch (...\node_modules\jsdom\lib\jsdom\living\window\navigation.js:74:3)

jsdom não oferece suporte à navegação, portanto, definir window.location.href ou similar apresentará esta mensagem. Não tenho certeza se Jest estava apenas suprimindo essas mensagens antes, ou o quê.

Isso é provavelmente algo que você deve corrigir em seus testes, porque significa que se você estivesse executando esses testes no navegador, o executor de teste ficaria completamente surpreso enquanto você navegasse na página para um novo URL, e você nunca veria nenhum teste resultados. Em vez disso, no jsdom, apenas enviamos uma mensagem para o console, que você pode ignorar se quiser, ou pode corrigir seus testes para fazê-los funcionar melhor em mais ambientes.

De qualquer forma, gostaria de adicionar mais documentação sobre isso para as pessoas, então deixarei este problema em aberto para fazer isso.

Entenda totalmente o que você está dizendo. A atualização recente do Jest 22 foi de JSDOM 9 para 11 IIRC, então o comportamento na versão 9.x pode ter sido bem diferente.

Tudo isso à parte, eu adoraria ver a navegação implementada em JSDOM com algum tipo de sinalização para torná-la autônoma em termos de carregamento de uma página diferente (em um espírito semelhante ao pushstate HTML5). A biblioteca é muito comumente usada para fins de teste portanto, embora talvez seja um pedido peculiar, seria usado com frequência.

Não acho que devemos adicionar um sinalizador que faça seus testes rodarem de forma diferente no jsdom do que nos navegadores. Então, seu material pode estar quebrado em navegadores (por exemplo, pode estar redirecionando usuários para alguma outra página, em vez de fazer a ação que seus testes veem acontecendo) e você nem perceberia!

Bem, neste caso não faria nada diferente, a não ser não descarregar o contexto da página atual. Ainda espero que window.location.href seja atualizado, etc.

@domenic Estou tendo o mesmo problema e queria saber se há algum tipo de prática recomendada para configurar o JSDOM com um aplicativo que define window.location . Pelo que posso dizer, JSDOM gera um erro ao tentar definir window.location e registra um erro ao tentar definir window.location.href - no entanto, estou lendo no mdn que os dois devem ser sinônimos. Devo atualizar o local de outra maneira que seja mais fácil de esboçar?
Grato pela ajuda 😅

Permita-me postar a resposta à minha própria pergunta 😁
Eu simplesmente substituo os usos de window.location = url; e window.location.href = url; por

window.location.assign(url);

e então em meus testes eu fiz:

sinon.stub(window.location, 'assign');
expect(window.location.assign).to.have.been.calledWith(url);

Funciona como um encanto - espero que possa ajudar outra pessoa 👍

Concordo que isso deve funcionar fora da caixa. Nós zombamos de window.location no FB, mas isso entra em conflito com a implementação de History do jsdom.

Como uma equipe pequena, certamente apreciaríamos a ajuda de projetos maiores que dependem de nós para implementar adequadamente a navegação no jsdom.

Se alguém estiver interessado, https://github.com/jsdom/jsdom/pull/1913 pode ser um bom lugar para começar.

A solução possível é contar com injeção / simulação de dependência para o objeto window em testes de unidade.

Algo como:

it('can test', () => {
  const mockWindow = {location: {href: null}};
  fn({window: mockWindow});
  expect(mockWindow.href).toEqual('something');
});

Isso não é ideal, mas como disse @domenic :

Isso é provavelmente algo que você deve corrigir em seus testes, porque significa que se você estivesse executando esses testes no navegador, o executor de teste ficaria completamente surpreso conforme você navegasse na página para um novo URL

Por enquanto vivemos com isso e sim mudamos nosso código de implementação para testes o que é considerado uma prática ruim mas também dormimos bem à noite!

Feliz teste

A solução de @hontas ajudou:

Eu usei window.location.assign(Config.BASE_URL); no meu código.

E aqui está o teste:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();

Mesmo problema, estou usando window.location.search = foo; no meu código e gostaria de testá-lo usando jsdom (e jest) 🤔

PS: Relacionado a https://github.com/facebook/jest/issues/5266

Após atualizar para jsdom 12.2.0, ocorreu um erro:
TypeError: Não é possível redefinir a propriedade: atribuir
em const assign = sinon.stub(document.location, 'assign')
como corrigi-lo?

@ yuri-sakharov

Após atualizar para jsdom 12.2.0, ocorreu um erro:
TypeError: Não é possível redefinir a propriedade: atribuir
em const assign = sinon.stub(document.location, 'assign')
como corrigi-lo?

sinon.stub(document.location, 'assign')

precisa ser:

sinon.stub(window.location, 'assign')

você precisa substituir document por window

Eu tenho a seguinte função

export const isLocalHost = () => Boolean(
  window.location.hostname === 'localhost' ||
  // [::1] is the IPv6 localhost address.
  window.location.hostname === '[::1]' ||
  // 127.0.0.1/8 is considered localhost for IPv4.
  window.location.hostname.match(
    /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
  )
);

para eu poder testar se funciona injeto diretamente o hostname

it('#isLocalHost should return true for all the cases of localhost', () => {
    window.location.hostname = 'localhost';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '[::1]';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '127.0.0.1';
    expect(isLocalHost()).toBeTruthy();

    // Reset back the hostname to avoid issues with it
    window.location.hostname = '';
  });

Mas estou recebendo este erro.

Não espero que jsdom implemente totalmente a navegação, mas pelo menos adicione as teclas e simule as funções.

Estou confuso sobre por que continuo recebendo esse erro, só quero poder configurar o valor.

@nickhallph
Substituí-o por window.location conforme você escreveu, mas o resultado é o mesmo
TypeError: Cannot redefine property: assign
Alguma ideia?

O mesmo problema que @ yuri-sakharov executando o mocha.

De forma alguma, pareço ser capaz de substituir / atualizar / simular ou fazer qualquer coisa com window.location.* . A única maneira de contornar isso é criar meu próprio mock window.location customizado e alterar todo o código-base para depender disso.

A solução de @hontas ajudou:

Eu usei window.location.assign(Config.BASE_URL); no meu código.

E aqui está o teste:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();

@zxiest 's versão Jest de @hontas' solução não funcionou para mim, mas isso fez:

window.location.assign = jest.fn();
expect(window.location.assign).toHaveBeenCalledWith('https://correct-uri.com');
window.location.assign.mockRestore();

Se você não quiser alterar seu código para usar location.assign - isso parece funcionar com JSDom 11 e 13 (embora haja uma chance de JSDom quebrá-lo no futuro ...)

delete window.location;
window.location = {}; // or stub/spy etc.

A última resposta funcionou para mim, mas eu tive que definir replace :

delete window.location
window.location = { replace: jest.fn() }

Espero que ajude.

Estou recebendo TypeError: não é possível redefinir propriedade: atribuir com sinon 7.2.3 e jsdom 13.2.0. Não tenho ideia de por que isso funciona para algumas pessoas e não para outras?

Isto é o que funcionou para mim:

    global.window = Object.create(window);
    const url = 'http://localhost';
    Object.defineProperty(window, 'location', {
      value: {
        href: url,
      },
      writable: true,
    });

Usamos pushState para fazer isso funcionar

 window.history.pushState(
        {},
        '',
        'http://localhost/something/123?order=asc'
      );

Essa é uma situação difícil. O JSDOM não oferece suporte total à navegação (exceto breadcrumbs) e o JSDOM não nos permite simulação de navegação. O resultado final é que não consigo escrever testes que, em última análise, tentem acionar a navegação .

Se JSDOM aprendeu a navegar (nem tenho certeza do que isso significa), talvez eu pudesse afirmar sobre estar na página apropriada. Para meus casos de uso de teste, porém, afirmar que a navegação foi acionada, em vez de realmente executada, é muito mais limpo / rápido. É o que fiz historicamente ao testar usando jsdom e agora está quebrado.

Permita-me postar a resposta à minha própria pergunta
Eu simplesmente substituo os usos de window.location = url; e window.location.href = url; por

window.location.assign(url);

e então em meus testes eu fiz:

sinon.stub(window.location, 'assign');
expect(window.location.assign).to.have.been.calledWith(url);

Funciona como um encanto - espero que possa ajudar outra pessoa

Isso se você tiver controle para a url de destino, mas e se você estiver carregando o site do google ou Instagram. ou algum site? como você pode resolver esse problema?

Com base na resposta de @chrisbateman , consegui fazer isso funcionar em um ambiente Jest. Para qualquer pessoa que esteja usando o Jest, aqui está minha solução alternativa:

describe('', () => {
    const originalLocation = window.location;

    beforeEach(() => {
        delete window.location;

        window.location = {
            href: '',
        };
    });

    afterEach(() => {
        window.location = originalLocation;
    });

    it('', () => {
        // test here
    });
});

Resolvi esse problema usando esta configuração. Eu queria testar o redirecionamento de urls hash:

    beforeEach(() => {
      delete global.window;
      global.window = {
        location: { replace: jest.fn(url => ({ href: url })) },
      };
    });
    it('should redirect hash url', () => {
      window.location.hash = '#/contrat?id=8171675304';
      global.window.location.href =
        'http://localhost:3000/#/contrat?id=8171675304';
      redirectHashUrl();

      expect(window.location.replace).toHaveBeenCalled();
    });

@chrisbateman @hamzahamidi obrigado, as soluções funcionaram bem.

Talvez não seja uma boa prática, mas temos alguns testes, que dependem de localização / host / nome do host e outras propriedades de localização. Então, zombar da localização como desejamos e restaurar depois funcionou para mim.

const realLocation = window.location;

describe('bla bla', () => {
  afterEach(() => {
    window.location = realLocation;
  });

  it('test where I want to use hostname', () => {
    delete window.location;
    window.location = { 
      hostname: 'my-url-i-expect.com'
    };
    // check my function that uses hostname
  });
});

Isto é o que funcionou para mim:

    global.window = Object.create(window);
    const url = 'http://localhost';
    Object.defineProperty(window, 'location', {
      value: {
        href: url,
      },
      writable: true,
    });

não funciona em um CI como circleci

A solução de @hontas ajudou:

Eu usei window.location.assign(Config.BASE_URL); no meu código.

E aqui está o teste:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();

Obrigado cara, você me salvou um dia! :)

No meu caso, estou testando a string de consulta e, como o assunto da minha especificação É a string de consulta em si, não quero ignorá-la, mas estou feliz em removê-la. no meu caso funcionou bem:

    let name = "utm_content"
    window.history.pushState({}, 'Test Title', '/test.html?utm_content=abc');
    expect(ParseUrlUtils.getParam(name)).toBe("abc")

Observe que aqui não importa qual é o título da janela e também não importa se é /test.html (que não é real), tudo o que importa é que a string de consulta seja buscada corretamente ( isso passa)

O problema com os exemplos é que os métodos e getters não são usados. O que eu faço é basicamente substituir o objeto Location pelo objeto URL. URL possui todas as propriedades de Location (search, host, hash, etc).

const realLocation = window.location;

describe('My test', () => {

    afterEach(() => {
        window.location = realLocation;
    });

    test('My test func', () => {

        // @ts-ignore
        delete window.location;

        // @ts-ignore
        window.location = new URL('http://google.com');

        // ...
    });
});

Estou recebendo este erro também

Error: Not implemented: navigation (except hash changes)
    at module.exports (...\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
    at navigateFetch (...\node_modules\jsdom\lib\jsdom\living\window\navigation.js:74:3)

A solução de @hontas ajudou:

Eu usei window.location.assign(Config.BASE_URL); no meu código.

E aqui está o teste:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();

Isso funcionou para mim, obrigado! No entanto, eu tive que usar o argumento done de jest test (), caso contrário, não teria certeza de que a expectativa foi avaliada e o teste poderia ter terminado com sucesso de qualquer maneira:

it('should', (done) => {
jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
   done();
})

window.location.assign.mockClear();
}

@hontas não funciona para mim :( Não foi possível reescrever, atribuir / substituir propriedade

Existe alguma maneira de descobrir qual teste está acionando a mensagem "Não implementado: navegação"? Eu tenho um conjunto de 43 testes - o erro está sendo exibido apenas uma vez e continua retornando. Não consigo dizer qual teste consertar !!! O rastreamento de pilha não me dá nenhuma indicação do culpado:

console.error
  Error: Not implemented: navigation (except hash changes)
      at module.exports (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
      at navigateFetch (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
      at exports.navigate (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
      at Timeout._onTimeout (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
      at listOnTimeout (internal/timers.js:531:17)
      at processTimers (internal/timers.js:475:7) undefined

    at VirtualConsole.<anonymous> (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
    at module.exports (node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12:26)
    at navigateFetch (node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
    at exports.navigate (node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
    at Timeout._onTimeout (node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)

@hontas não funciona para mim :( Não foi possível reescrever, atribuir / substituir propriedade

Pelo que entendi, jest mudou algo na nova versão ("jest": "^ 26.0.1"), então isso funciona agora:

// Mock
  Object.defineProperty(window, 'location', {
    value: {
      pathname: '/terminals',
      assign: jest.fn(),
    },
  });

// Then test
expect(window.location.assign).toBeCalledWith('/auth');

Existe alguma maneira de descobrir qual teste está acionando a mensagem "Não implementado: navegação"? Eu tenho um conjunto de 43 testes - o erro está sendo exibido apenas uma vez e continua retornando. Não consigo dizer qual teste consertar !!! O rastreamento de pilha não me dá nenhuma indicação do culpado:

console.error
  Error: Not implemented: navigation (except hash changes)
      at module.exports (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
      at navigateFetch (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
      at exports.navigate (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
      at Timeout._onTimeout (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
      at listOnTimeout (internal/timers.js:531:17)
      at processTimers (internal/timers.js:475:7) undefined

    at VirtualConsole.<anonymous> (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
    at module.exports (node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12:26)
    at navigateFetch (node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
    at exports.navigate (node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
    at Timeout._onTimeout (node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)

É difícil dizer de qual erro de teste não implementado vem.

Eu adiciono um ponto de interrupção em node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12 , executo a sessão de depuração e espero até que o ponto de interrupção seja alcançado. Só então vejo qual teste devo melhorar para me livrar dessa mensagem.
image

Às vezes, leva de 2 a 3 minutos até que o corredor chegue ao teste com um problema.

PS: No meu projeto atual, existem 176 testes relacionados a jsdom

No momento, estou usando Jest 26.0.1 e o seguinte funciona para mim:

  1. Use window.location.assign(url) para alterar a localização da janela.

  2. Simule o objeto de localização da seguinte forma (observe que não excluir e reconstruir o objeto primeiro ainda resulta no erro não implementado na versão anotada de Jest e anteriores, eu acho, também Object.defineProperty não funciona e ainda resulta no erro):

delete window.location;
window.location = {
    href: '',
    hostname: '',
    pathname: '',
    protocol: '',
    assign: jest.fn()
};
  1. Afirmar com:
expect(window.location.assign).toBeCalledWith(url);

Seria muito bom se a brincadeira nos permitisse zombar facilmente da localização sem todos esses problemas e mudanças constantes. Eu estava usando apenas window.location.assign = jest.fn() sem problemas por um longo tempo, então eu atualizei da v24 e agora esta.

Existe um bom motivo para as APIs do Windows terem sido bloqueadas / congeladas?
Parece que essa é a barreira para as pessoas que tentam contornar a navegação que não está sendo implementada.

Se não, podemos simplesmente não congelá-los? Acho que nem mesmo os navegadores são tão rígidos em impedir que você altere os objetos da janela.

Se você tiver um exemplo de jsdom se comportando de maneira diferente dos navegadores, registre um problema seguindo o modelo de problema (incluindo o jsbin ou semelhante mostrando o comportamento do navegador).

Alguém mais está encontrando o mesmo Error: Not implemented: navigation (except hash changes) durante os testes de Jest que disparam um evento de clique em um elemento âncora com um href ? Para meus testes, estou espionando uma função chamada onClick e fazendo afirmações sobre isso, então preciso realmente disparar o evento click no elemento âncora.

As soluções acima relacionadas à simulação de window.location funcionam para mim onde estou explicitamente chamando window.location.replace ou window.location.assign , mas não ajudam neste caso em que a navegação se origina de um elemento âncora sendo clicado.

Alguma ideia sobre soluções? Obrigado!

Como você testaria esse código em um navegador? Lembre-se de que, no navegador, clicar no link explodirá sua página inteira e jogará fora os resultados do teste. Portanto, o que quer que você faça para evitar isso, também evitará que a mensagem de aviso, muito menos dramática, jsdom seja enviada para o console.

Na minha situação, o projeto atualizou o Jest da versão 23 para a 26.
Tive um problema com location.search . Teve o mesmo erro Error: Not implemented: navigation .
Módulo que testei obtém valores de parâmetros de consulta de pesquisa.
A próxima implementação funcionou para mim:

beforeAll(() => {
  delete window.location;
  window.location = new URL('your URL');
})

afterAll(() => {
  window.location.search = '';
})

@mattcphillips , você descobriu como consertar isso? eu também estou tendo problemas ao clicar

@Sabrinovsky Não, acabei apenas adicionando um script ao setupFiles em minha configuração de brincadeira que engoliria os erros de console vindos da navegação jsdom para que não atrapalhassem nossos testes. Mais como uma bandaid do que qualquer outra coisa, mas parece que esses erros são esperados em nossa configuração de teste.

Aqui está o script que executo antes dos meus testes:

// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that orginate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', error => {
  if (
    error.type !== 'not implemented' &&
    error.message !== 'Not implemented: navigation (except hash changes)' &&
    originalListener
  ) {
    originalListener(error);
  }

  // swallow error
});

Isso funcionou para mim.

delete global.window.location
global.window.location = { href: 'https://test.com' }

Isso funcionou para mim

delete window.location
window.location = { assign: jest.fn() }

Se você obtiver TypeError: Cannot assign to read only property 'assign' of object '[object Location]' , use algo assim em seu jest.setup.ts :

`` `
global.window = Object.create (janela);
Object.defineProperty (janela, 'localização', {
valor: {
... window.location,
},
gravável: verdadeiro,
});
`` ``

Esta página foi útil?
0 / 5 - 0 avaliações