Jsdom: Erreur : Non implémenté : navigation

Créé le 12 janv. 2018  ·  49Commentaires  ·  Source: jsdom/jsdom

Après la récente mise jest niveau "Error: Not implemented:" navigation

Mon code repose sur window.location et j'utilise dans les tests :

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

Existe-t-il un moyen de définir une valeur de window.location.search utilisant la nouvelle version de jsdom ?

Commentaire le plus utile

Permettez-moi de poster la réponse à ma propre question
Je remplace simplement les usages de window.location = url; et window.location.href = url; par

window.location.assign(url);

et puis dans mes tests j'ai fait :

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

Fonctionne comme un charme - j'espère que cela pourra aider quelqu'un d'autre

Tous les 49 commentaires

j'ai aussi cette erreur

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 ne prend pas en charge la navigation, donc la définition de window.location.href ou similaire donnera ce message. Je ne sais pas si Jest supprimait simplement ces messages auparavant, ou quoi.

C'est probablement quelque chose que vous devriez corriger dans vos tests, car cela signifie que si vous exécutiez ces tests dans le navigateur, le testeur serait complètement époustouflé lorsque vous naviguiez sur la page vers une nouvelle URL, et vous ne verriez jamais aucun test résultats. Dans jsdom, à la place, nous affichons simplement un message sur la console, que vous pouvez ignorer si vous le souhaitez, ou vous pouvez corriger vos tests pour les faire mieux fonctionner dans davantage d'environnements.

Quoi qu'il en soit, j'aimerais ajouter plus de documentation à ce sujet pour les gens, donc je vais laisser ce problème ouvert pour suivre.

Comprenez parfaitement ce que vous dites. La récente mise à jour de Jest 22 est passée de JSDOM 9 à 11 IIRC, donc le comportement de la version 9.x aurait pu être assez différent.

Tout cela mis à part, j'aimerais voir la navigation implémentée dans JSDOM avec une sorte d'indicateur pour en faire un no-op en termes de chargement d'une page différente (dans un esprit similaire à HTML5 pushstate.) La bibliothèque est très couramment utilisée à des fins de test ainsi, bien qu'il s'agisse peut-être d'une demande excentrique, il serait souvent utilisé.

Je ne pense pas que nous devrions ajouter un indicateur qui rend vos tests différents dans jsdom que dans les navigateurs. Ensuite, votre contenu pourrait être cassé dans les navigateurs (par exemple, il pourrait rediriger les utilisateurs vers une autre page, au lieu de faire l'action que vos tests voient se produire) et vous ne le remarqueriez même pas !

Eh bien, dans ce cas, cela ne ferait rien de différent, à part ne pas décharger le contexte de la page actuelle. Je m'attendrais toujours à ce que window.location.href soit mis à jour, etc.

@domenic J'ai le même problème et je me demandais s'il y avait une sorte de meilleure pratique pour configurer JSDOM avec une application qui définit window.location . D'après ce que je peux dire, JSDOM génère une erreur en essayant de définir window.location et enregistre une erreur en essayant de définir window.location.href - mais je lis sur mdn que les deux devraient être synonymes. Dois-je mettre à jour l'emplacement d'une autre manière plus facile à remplacer ?
Merci pour l'aide

Permettez-moi de poster la réponse à ma propre question
Je remplace simplement les usages de window.location = url; et window.location.href = url; par

window.location.assign(url);

et puis dans mes tests j'ai fait :

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

Fonctionne comme un charme - j'espère que cela pourra aider quelqu'un d'autre

Convenez que cela devrait fonctionner hors de la boîte. Nous nous moquons de window.location sur FB, mais cela entre en conflit avec l'implémentation de History jsdom.

En tant que petite équipe, nous apprécierions certainement l'aide des grands projets qui dépendent de nous pour implémenter correctement la navigation dans jsdom.

Si quelqu'un est intéressé, https://github.com/jsdom/jsdom/pull/1913 pourrait être un bon point de départ.

La solution possible consiste à s'appuyer sur l'injection/l'imitation de dépendance pour l'objet window dans les tests unitaires.

Quelque chose comme:

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

Ce n'est pas idéal mais comme dit @domenic :

C'est probablement quelque chose que vous devriez corriger dans vos tests, car cela signifie que si vous exécutiez ces tests dans le navigateur, le testeur serait complètement époustouflé lorsque vous naviguiez sur la page vers une nouvelle URL

Pour l'instant on vit avec ça et oui on change notre code d'implémentation pour les tests ce qui est considéré comme une mauvaise pratique mais on dort aussi bien la nuit !

Bon test

La solution de @hontas a aidé :

J'ai utilisé window.location.assign(Config.BASE_URL); dans mon code.

Et voici le test :

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

window.location.assign.mockClear();

Même problème, j'utilise window.location.search = foo; dans mon code et j'aimerais le tester en utilisant jsdom (et jest) 🤔

PS : lié à https://github.com/facebook/jest/issues/5266

Après la mise à jour vers jsdom 12.2.0 a obtenu l'erreur :
TypeError : Impossible de redéfinir la propriété : attribuer
sur const assign = sinon.stub(document.location, 'assign')
comment le réparer?

@yuri-sakharov

Après la mise à jour vers jsdom 12.2.0 a obtenu l'erreur :
TypeError : Impossible de redéfinir la propriété : attribuer
sur const assign = sinon.stub(document.location, 'assign')
comment le réparer?

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

doit être :

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

vous devez remplacer document par window

j'ai la fonction suivante

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

pour que je puisse tester que ça marche j'injecte directement le 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 = '';
  });

Mais j'obtiens cette erreur.

Je ne m'attends pas à ce que jsdom implémente complètement la navigation, mais ajoute au moins les touches et se moque des fonctions.

Je ne comprends pas pourquoi je continue à recevoir cette erreur, je veux juste pouvoir configurer la valeur.

@nickhallph
Je l'ai remplacé par window.location comme vous l'avez écrit mais le résultat est le même
TypeError: Cannot redefine property: assign
Des idées?

Même problème que @yuri-sakharov exécutant moka.

Je ne semble en aucun cas capable de remplacer/mettre à jour/simuler ou de faire quoi que ce soit avec window.location.* . La seule façon que je vois autour de cela est de créer ma propre maquette personnalisée window.location et de modifier l'ensemble de la base de code pour en dépendre.

La solution de @hontas a aidé :

J'ai utilisé window.location.assign(Config.BASE_URL); dans mon code.

Et voici le test :

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

window.location.assign.mockClear();

' La version de Jest de @hontas de @zxiest solution ne fonctionne pas pour moi, mais cela a fait:

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

Si vous ne voulez pas modifier votre code pour utiliser location.assign - cela semble fonctionner avec JSDom 11 et 13 (bien qu'il y ait une chance que JSDom puisse le casser à l'avenir...)

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

La dernière réponse a fonctionné pour moi, mais j'ai dû définir replace :

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

J'espère que ça aide.

J'obtiens TypeError: Cannot redefine property: assign with sinon 7.2.3 et jsdom 13.2.0. Aucune idée pourquoi cela fonctionne pour certaines personnes et pas pour d'autres?

C'est ce qui a fonctionné pour moi:

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

Nous avons utilisé pushState pour que cela fonctionne

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

C'est une situation difficile. JSDOM ne prend pas entièrement en charge la navigation (autre que le fil d'Ariane) et JSDOM ne nous permet pas de simuler la navigation. Le résultat final est que je ne peux pas écrire de tests qui tentent finalement de déclencher la navigation .

Si JSDOM apprenait à naviguer (je ne suis même pas sûr de ce que cela signifie), je pourrais peut-être affirmer que je suis sur la bonne page. Pour mes cas d'utilisation de test, cependant, affirmer que la navigation a été déclenchée, plutôt que réellement effectuée, est beaucoup plus propre/plus rapide. C'est ce que j'ai fait historiquement lors de tests avec jsdom et maintenant c'est cassé.

Permettez-moi de poster la réponse à ma propre question
Je remplace simplement les usages de window.location = url; et window.location.href = url; par

window.location.assign(url);

et puis dans mes tests j'ai fait :

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

Fonctionne comme un charme - j'espère que cela pourra aider quelqu'un d'autre

Que si vous avez le contrôle sur l'URL cible, mais que se passe-t-il si vous chargez le site Web de Google ou d'Instagram. ou n'importe quel site web ? comment pouvez-vous résoudre ce problème ?

Sur la base de la réponse de @chrisbateman, j'ai réussi à faire fonctionner cela dans un environnement Jest. Pour tous ceux qui utilisent Jest, voici ma solution de contournement :

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

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

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

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

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

J'ai résolu ce problème en utilisant cette configuration. Je voulais tester la redirection pour les URL de hachage :

    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 merci, ces solutions ont bien fonctionné.

Ce n'est peut-être pas une bonne pratique, mais nous avons quelques tests, ceux-ci reposent sur l'emplacement/l'hôte/le nom d'hôte et d'autres propriétés d'emplacement. Donc se moquer de l'emplacement comme nous le voulons et restaurer ensuite a fonctionné pour moi.

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

C'est ce qui a fonctionné pour moi:

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

ça ne marche pas sur un CI comme circleci

La solution de @hontas a aidé :

J'ai utilisé window.location.assign(Config.BASE_URL); dans mon code.

Et voici le test :

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

window.location.assign.mockClear();

Merci mon pote, tu m'as sauvé une journée! :)

Dans mon cas, je teste la chaîne de requête, et puisque le sujet de ma spécification EST la chaîne de requête elle-même, je ne veux pas la contourner, mais je suis heureux de la supprimer. dans mon cas cela a bien fonctionné :

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

Notez qu'ici peu importe le titre de la fenêtre et peu importe qu'il s'agisse de /test.html (ce qui n'est pas réel), tout ce qui compte est que la chaîne de requête soit extraite correctement ( ça passe)

Le problème avec les exemples est que les méthodes et les getters ne sont pas utilisés. Ce que je fais, c'est essentiellement remplacer l'objet Location par l'objet URL. L'URL a toutes les propriétés de l'emplacement (recherche, hôte, hachage, 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');

        // ...
    });
});

j'ai aussi cette erreur

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 solution de @hontas a aidé :

J'ai utilisé window.location.assign(Config.BASE_URL); dans mon code.

Et voici le test :

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

window.location.assign.mockClear();

Cela a fonctionné pour moi, merci! Cependant, j'ai dû utiliser l'argument done de jest test(), sinon je ne serais pas sûr que l'attente ait été évaluée et que le test se soit terminé avec succès de toute façon :

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

window.location.assign.mockClear();
}

@hontas ne fonctionne pas pour moi :( Impossible de réécrire la propriété assigner/remplacer

Existe-t-il un moyen de savoir quel test déclenche le message « Non implémenté : navigation » ? J'ai une suite de 43 tests - l'erreur ne s'affiche qu'une seule fois et elle ne cesse de rebondir. Je ne sais pas quel test corriger !!! La trace de la pile ne me donne aucune indication sur le coupable :

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 ne fonctionne pas pour moi :( Impossible de réécrire la propriété assigner/remplacer

Si je comprends bien, jest a changé quelque chose dans la nouvelle version ("jest": "^26.0.1"), donc cela fonctionne maintenant :

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

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

Existe-t-il un moyen de savoir quel test déclenche le message « Non implémenté : navigation » ? J'ai une suite de 43 tests - l'erreur ne s'affiche qu'une seule fois et elle ne cesse de rebondir. Je ne sais pas quel test corriger !!! La trace de la pile ne me donne aucune indication sur le coupable :

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)

Il est difficile de dire de quel test non implémenté l'erreur provient.

J'ajoute un point d'arrêt à node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12 , lance la session de débogage et attend que le point d'arrêt soit atteint. Ce n'est qu'alors que je vois quel test je dois améliorer pour me débarrasser de ce message.
image

Parfois, cela prend 2-3 minutes jusqu'à ce que le coureur atteigne le test avec un problème.

PS : dans mon projet actuel, il y a 176 tests liés à jsdom

J'utilise actuellement Jest 26.0.1 et ce qui suit fonctionne pour moi :

  1. Utilisez window.location.assign(url) pour changer l'emplacement de la fenêtre.

  2. Moquez-vous de l'objet de localisation comme suit (notez que le fait de ne pas supprimer et reconstruire l'objet en premier entraîne toujours l'erreur non implémentée dans la version notée de Jest et plus tôt, je pense, également Object.defineProperty ne fonctionne pas et entraîne toujours le Erreur):

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

Ce serait vraiment bien si la plaisanterie nous permettait de se moquer facilement de l'emplacement sans tous ces problèmes et changements constants. J'utilisais auparavant seulement window.location.assign = jest.fn() sans aucun problème pendant longtemps, puis j'ai mis à niveau la v24 et maintenant ceci.

Y a-t-il une bonne raison pour laquelle les API Windows ont été verrouillées/gelées ?
Il semble que ce soit l'obstacle pour les personnes essayant de contourner la navigation qui n'est pas implémentée.

Sinon, ne pouvons-nous tout simplement pas les congeler? Je ne pense pas que même les navigateurs soient aussi stricts pour vous empêcher de modifier les objets de la fenêtre.

Si vous avez un exemple de jsdom se comportant différemment des navigateurs, veuillez déposer un problème en suivant le modèle de problème (y compris le jsbin ou un comportement similaire montrant le navigateur).

Est-ce que quelqu'un d'autre rencontre le même Error: Not implemented: navigation (except hash changes) lors des tests Jest qui déclenchent un événement click sur un élément d'ancrage avec un href ? Pour mes tests, j'espionne une fonction qui s'appelle onClick et je fais des affirmations à ce sujet, je dois donc déclencher l'événement click sur l'élément d'ancrage.

Les solutions ci-dessus liées à la moquerie de window.location fonctionnent pour moi où j'appelle explicitement window.location.replace ou window.location.assign , mais n'aident pas dans ce cas où la navigation provient d'un élément d'ancrage étant cliqué.

Des idées de solutions ? Merci!

Comment testeriez-vous ce code dans un navigateur ? Gardez à l'esprit que dans le navigateur, cliquer sur le lien ferait exploser toute votre page et supprimerait tous les résultats des tests. Donc, quoi que vous fassiez pour empêcher cela, empêchera également le message d'avertissement beaucoup moins dramatique que jsdom envoie à la console.

Dans ma situation, le projet a mis à niveau Jest de la version 23 à la version 26.
J'ai eu un problème avec location.search . Avait la même erreur Error: Not implemented: navigation .
Le module que j'ai testé obtient les valeurs des paramètres de requête de recherche.
L'implémentation suivante a fonctionné pour moi :

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

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

@mattcphillips avez-vous trouvé comment résoudre ce problème ? j'ai aussi un problème en cliquant

@Sabrinovsky Non, j'ai fini par ajouter un script au setupFiles dans ma configuration jest qui avalerait les erreurs de console provenant de la navigation jsdom afin qu'elles n'encombrent pas nos tests. Plus un pansement qu'autre chose, mais il semblait que ces erreurs étaient à prévoir dans notre configuration de test.

Voici le script que j'exécute avant mes tests :

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

Cela a fonctionné pour moi.

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

Cela a fonctionné pour moi

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

Si vous obtenez TypeError: Cannot assign to read only property 'assign' of object '[object Location]' , utilisez quelque chose comme ceci dans votre jest.setup.ts :

```
global.window = Object.create(window);
Object.defineProperty(fenêtre, 'emplacement', {
valeur: {
...emplacement.de.la.fenêtre,
},
inscriptible : vrai,
});
````

Cette page vous a été utile?
0 / 5 - 0 notes