Jsdom: Error: no implementado: navegación

Creado en 12 ene. 2018  ·  49Comentarios  ·  Fuente: jsdom/jsdom

Después de la actualización reciente jest (que usa jsdom en segundo plano) de la versión 21.2.0 a 22.0.6, comencé a recibir el error: "Error: Not implemented:" navigation

Mi código se basa en window.location y lo uso en las pruebas:

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

¿Hay alguna manera de definir un valor de window.location.search usando la nueva versión de jsdom?

Comentario más útil

Permítame publicar la respuesta a mi propia pregunta 😁
Simplemente reemplazo los usos de window.location = url; y window.location.href = url; con

window.location.assign(url);

y luego en mis pruebas hice:

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

Funciona a las mil maravillas, espero que pueda ser de ayuda para otra persona 👍

Todos 49 comentarios

Yo también recibo este error

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 no admite la navegación, por lo que la configuración de window.location.href o similar dará este mensaje. No estoy seguro de si Jest solo estaba suprimiendo estos mensajes antes, o qué.

Esto es probablemente algo que debería corregir en sus pruebas, porque significa que si estuviera ejecutando esas pruebas en el navegador, el ejecutor de la prueba quedaría completamente impresionado mientras navegaba por la página a una nueva URL, y nunca vería ninguna prueba. resultados. En jsdom, en cambio, solo enviamos un mensaje a la consola, que puede ignorar si lo desea, o puede corregir sus pruebas para que funcionen mejor en más entornos.

De todos modos, me gustaría agregar más documentación sobre esto para las personas, así que dejaré este problema abierto para realizar un seguimiento.

Entiende totalmente lo que estás diciendo. La reciente actualización de Jest 22 pasó de JSDOM 9 a 11 IIRC, por lo que el comportamiento en 9.x podría haber sido bastante diferente.

Dejando todo eso a un lado, me encantaría ver la navegación implementada en JSDOM con algún tipo de bandera para que no sea una operación en términos de cargar una página diferente (en un espíritu similar al pushstate de HTML5). La biblioteca se usa con mucha frecuencia con fines de prueba. por lo que, aunque quizás sea una solicitud peculiar, se usaría con frecuencia.

No creo que debamos agregar una bandera que haga que sus pruebas se ejecuten de manera diferente en jsdom que en los navegadores. Entonces, sus cosas podrían romperse en los navegadores (por ejemplo, podría redirigir a los usuarios a otra página, en lugar de realizar la acción que sus pruebas ven) ¡y ni siquiera lo notaría!

Bueno, en este caso no haría nada diferente, aparte de no descargar el contexto de la página actual. Todavía espero que window.location.href se actualice, etc.

@domenic Tengo el mismo problema y me preguntaba si existe algún tipo de práctica recomendada para configurar JSDOM con una aplicación que establezca window.location . Por lo que puedo decir, JSDOM arroja un error al intentar establecer window.location y registra un error al intentar establecer window.location.href ; sin embargo, estoy leyendo en mdn que los dos deberían ser sinónimos. ¿Debería actualizar la ubicación de otra manera que sea más fácil de eliminar?
Agradecido por la ayuda 😅

Permítame publicar la respuesta a mi propia pregunta 😁
Simplemente reemplazo los usos de window.location = url; y window.location.href = url; con

window.location.assign(url);

y luego en mis pruebas hice:

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

Funciona a las mil maravillas, espero que pueda ser de ayuda para otra persona 👍

De acuerdo, esto debería funcionar fuera de la caja. Nos burlamos de window.location en FB, pero eso entra en conflicto con la implementación History jsdom.

Como equipo pequeño, ciertamente apreciaríamos la ayuda de los proyectos más grandes que dependen de nosotros para implementar correctamente la navegación en jsdom.

Si alguien está interesado, https://github.com/jsdom/jsdom/pull/1913 podría ser un buen lugar para comenzar.

La posible solución es confiar en la inyección / simulación de dependencia para el objeto window en las pruebas unitarias.

Algo como:

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

Esto no es ideal, pero como lo dijo @domenic :

Esto es probablemente algo que debería corregir en sus pruebas, porque significa que si estuviera ejecutando esas pruebas en el navegador, el ejecutor de la prueba quedaría completamente impresionado mientras navegaba por la página hacia una nueva URL.

Por ahora vivimos con esto y sí, cambiamos nuestro código de implementación para las pruebas, lo cual se considera una mala práctica, ¡pero también dormimos bien por la noche!

Feliz prueba

La solución de @hontas ayudó:

Usé window.location.assign(Config.BASE_URL); en mi código.

Y aquí está la prueba:

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

window.location.assign.mockClear();

El mismo problema, estoy usando window.location.search = foo; en mi código y me gustaría probarlo usando jsdom (y broma) 🤔

PD: Relacionado con https://github.com/facebook/jest/issues/5266

Después de actualizar a jsdom 12.2.0 obtuvo el error:
TypeError: no se puede redefinir la propiedad: asignar
en const assign = sinon.stub(document.location, 'assign')
¿Como arreglarlo?

@ yuri-sajarov

Después de actualizar a jsdom 12.2.0 obtuvo el error:
TypeError: no se puede redefinir la propiedad: asignar
en const assign = sinon.stub(document.location, 'assign')
¿Como arreglarlo?

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

necesita ser:

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

necesita reemplazar document con window

Tengo la siguiente función

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 que pueda probar que funciona, inyecto directamente el 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 = '';
  });

Pero recibo este error.

No espero que jsdom implemente completamente la navegación, pero al menos agregue las teclas y simule las funciones.

Estoy confundido sobre por qué sigo recibiendo este error, solo quiero poder configurar el valor.

@nickhallph
Lo reemplacé por window.location como escribiste, pero el resultado es el mismo
TypeError: Cannot redefine property: assign
¿Algunas ideas?

El mismo problema que @ yuri-sakharov ejecutando mocha.

De ninguna manera parece que pueda reemplazar / actualizar / simular o hacer algo con window.location.* . La única forma en que veo esto es crear mi propio window.location mock personalizado y cambiar todo el código base para depender de eso.

La solución de @hontas ayudó:

Usé window.location.assign(Config.BASE_URL); en mi código.

Y aquí está la prueba:

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

window.location.assign.mockClear();

@zxiest 's versión de la broma de @hontas' solución no funcionó para mí, pero esto hizo:

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

Si no desea cambiar su código para usar location.assign , esto parece funcionar con JSDom 11 y 13 (aunque existe la posibilidad de que JSDom lo rompa en el futuro ...)

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

La última respuesta funcionó para mí, pero tuve que definir replace :

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

Espero eso ayude.

Recibo TypeError: No se puede redefinir la propiedad: asignar con sinon 7.2.3 y jsdom 13.2.0. ¿No tienes idea de por qué esto funciona para algunas personas y no para otras?

Esto es lo que funcionó para mí:

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

Usamos pushState para que esto funcione

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

Esta es una situación dificil. JSDOM no es totalmente compatible con la navegación (aparte de las rutas de navegación) y JSDOM no nos permite simular la navegación. El resultado final es que no puedo escribir pruebas que, en última instancia, intenten activar la navegación .

Si JSDOM aprendió a navegar (ni siquiera estoy seguro de lo que eso significa), tal vez podría afirmar que estoy en la página adecuada. Sin embargo, para mis casos de uso de prueba, afirmar que la navegación se activó, en lugar de realizarla realmente, es mucho más limpio / rápido. Es lo que he hecho históricamente al probar con jsdom y ahora está roto.

Permítame publicar la respuesta a mi propia pregunta.
Simplemente reemplazo los usos de window.location = url; y window.location.href = url; con

window.location.assign(url);

y luego en mis pruebas hice:

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

Funciona a las mil maravillas, espero que pueda ser de ayuda para otra persona.

Eso si tiene el control de la URL de destino, pero ¿qué pasa si está cargando el sitio web de Google o Instagram? o cualquier sitio web? ¿Cómo puedes solucionar este problema?

Según la respuesta de @chrisbateman , logré que esto funcionara en un entorno Jest. Para cualquiera que esté usando Jest, aquí está mi solución:

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

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

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

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

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

Resolví este problema usando esta configuración. Quería probar la redirección para las URL 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 gracias, las soluciones funcionaron bien.

Tal vez no sea una buena práctica, pero tenemos algunas pruebas, que se basan en la ubicación / host / nombre de host y otras propiedades de ubicación. Así que burlarse de la ubicación que queremos y restaurar después funcionó para mí.

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

Esto es lo que funcionó para mí:

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

no funciona en un CI como circleci

La solución de @hontas ayudó:

Usé window.location.assign(Config.BASE_URL); en mi código.

Y aquí está la prueba:

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

window.location.assign.mockClear();

¡Gracias amigo, me has salvado un día! :)

En mi caso, estoy probando la cadena de consulta, y dado que el tema de mi especificación ES la cadena de consulta en sí, no quiero omitirla, pero estoy feliz de eliminarla. en mi caso esto funcionó bien:

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

Tenga en cuenta que aquí no importa cuál sea el título de la ventana y tampoco importa que sea /test.html (que no es real), todo lo que importa es que la cadena de consulta se obtenga correctamente ( esto pasa)

El problema con los ejemplos es que los métodos y captadores no se utilizan. Lo que hago es básicamente reemplazar el objeto Ubicación por el objeto URL. La URL tiene todas las propiedades de la ubicación (búsqueda, 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');

        // ...
    });
});

Yo también recibo este error

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)

La solución de @hontas ayudó:

Usé window.location.assign(Config.BASE_URL); en mi código.

Y aquí está la prueba:

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

window.location.assign.mockClear();

Esto funcionó para mí, ¡gracias! Sin embargo, tuve que usar el argumento done de jest test (), de lo contrario, no estaría seguro de que se evaluó la expectativa y que la prueba podría haber terminado con éxito de todos modos:

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

window.location.assign.mockClear();
}

@hontas No funciona para mí :( No se puede reescribir la propiedad de asignación / reemplazo

¿Hay alguna forma de averiguar qué prueba está activando el mensaje "No implementado: navegación"? Tengo un conjunto de 43 pruebas: el error se muestra solo una vez y sigue rebotando. ¡No puedo decir qué prueba corregir! El seguimiento de la pila no me da ninguna indicación del culpable:

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 No funciona para mí :( No se puede reescribir la propiedad de asignación / reemplazo

Según tengo entendido, jest cambió algo en la nueva versión ("jest": "^ 26.0.1"), así que esto funciona ahora mismo:

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

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

¿Hay alguna forma de averiguar qué prueba está activando el mensaje "No implementado: navegación"? Tengo un conjunto de 43 pruebas: el error se muestra solo una vez y sigue rebotando. ¡No puedo decir qué prueba corregir! El seguimiento de la pila no me da ninguna indicación del culpable:

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)

Es difícil saber de qué prueba no implementada proviene el error.

Agrego un punto de interrupción en node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12 , ejecuto la sesión de depuración y espero hasta que se alcanza el punto de interrupción. Solo entonces veo qué prueba debo mejorar para deshacerme de este mensaje.
image

A veces, toma de 2 a 3 minutos hasta que el corredor llega a la prueba con un problema.

PD: en mi proyecto actual hay 176 pruebas relacionadas con jsdom

Actualmente estoy usando Jest 26.0.1 y lo siguiente me funciona:

  1. Utilice window.location.assign(url) para cambiar la ubicación de la ventana.

  2. Simule el objeto de ubicación de la siguiente manera (tenga en cuenta que no eliminar y reconstruir el objeto primero todavía da como resultado el error no implementado en la versión anotada de Jest y antes, creo, también Object.defineProperty no funciona y aún da como resultado el error):

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

Sería realmente bueno si Jest nos permitiera simular fácilmente la ubicación sin todos estos problemas y cambios constantes. Anteriormente estaba usando solo window.location.assign = jest.fn() sin ningún problema durante mucho tiempo, luego actualicé desde v24 y ahora esto.

¿Hay una buena razón por la que las API de Windows se bloquearon / congelaron?
Parece que esa es la barrera para las personas que intentan evitar que la navegación no se implemente.

Si no es así, ¿podemos simplemente no congelarlos? No creo que ni siquiera los navegadores sean tan estrictos para evitar que altere los objetos de la ventana.

Si tiene un ejemplo de jsdom que se comporta de manera diferente a los navegadores, presente un problema siguiendo la plantilla de problemas (incluido el jsbin o similar que muestra el comportamiento del navegador).

¿Alguien más se encuentra con el mismo Error: Not implemented: navigation (except hash changes) durante las pruebas de Jest que disparan un evento de clic en un elemento de anclaje con un href ? Para mis pruebas, estoy espiando una función que se llama onClick y hago afirmaciones sobre eso, así que necesito activar el evento de clic en el elemento de anclaje.

Las soluciones anteriores relacionadas con burlarse de window.location funcionan para mí cuando llamo explícitamente a window.location.replace o window.location.assign , pero no ayudan en este caso en el que la navegación se origina en un elemento de anclaje haciendo clic.

¿Alguna idea de soluciones? ¡Gracias!

¿Cómo probarías ese código en un navegador? Tenga en cuenta que en el navegador, al hacer clic en el enlace, se perderá toda la página y se descartarán los resultados de las pruebas. Entonces, cualquier cosa que haga para evitar eso, también evitará el mensaje de advertencia mucho menos dramático que jsdom envía a la consola.

En mi situación, el proyecto actualizó Jest de la versión 23 a la 26.
Tuve un problema con location.search . Tuvo el mismo error Error: Not implemented: navigation .
El módulo que probé obtiene valores de parámetros de consulta de búsqueda.
La siguiente implementación funcionó para mí:

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

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

@mattcphillips, ¿has descubierto cómo solucionar este problema? También tengo problemas al hacer clic

@Sabrinovsky No, terminé agregando un script a setupFiles en mi configuración de broma que se tragaría los errores de la consola provenientes de la navegación jsdom para que no abarrotaran nuestras pruebas. Más una curita que cualquier otra cosa, pero parecía que estos errores eran de esperar en nuestra configuración de prueba.

Aquí está el script que ejecuto antes de mis pruebas:

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

Esto funcionó para mí.

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

Esto funcionó para mi

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

Si obtiene TypeError: Cannot assign to read only property 'assign' of object '[object Location]' , entonces use algo como esto en su jest.setup.ts :

''
global.window = Object.create (ventana);
Object.defineProperty (ventana, 'ubicación', {
valor: {
... ubicación.de.ventana,
},
escribible: verdadero,
});
`` ``

¿Fue útil esta página
0 / 5 - 0 calificaciones