Jest: window.location.href ne peut pas être modifié dans les tests.

Créé le 13 avr. 2016  ·  70Commentaires  ·  Source: facebook/jest

Salut @cpojer ,

C'est en fait plus un problème de jsdom@8 ... voir tmpvar/jsdom#1388, mais je veux épingler ici aussi pour que Jest récupère la solution que jsdom propose.

Auparavant, avec [email protected]/[email protected] , vous pouviez écrire un test comme celui-ci :

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

Et ce test passerait. Depuis jsdom@8 ce n'est plus possible et ces tests échouent.

On dirait que jsdom envisage un certain type de capacité, je voulais juste m'assurer que Jest reprendra cette capacité lorsqu'elle sera disponible.

Commentaire le plus utile

Vous avez raison, il s'agit bien d'un problème jsdom. Chez Facebook, ce que nous avons fait pour contourner ce problème, c'est d'utiliser ceci :

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

cela fonctionne pour nous, mais nous sommes toujours sur jsdom 7 en interne.

Je vais fermer ceci, car je pense que la façon de faire Object.defineProperty est bonne. Si cela ne fonctionne pas pour vous dans jsdom 8, je serai heureux de le rouvrir.

Tous les 70 commentaires

Vous avez raison, il s'agit bien d'un problème jsdom. Chez Facebook, ce que nous avons fait pour contourner ce problème, c'est d'utiliser ceci :

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

cela fonctionne pour nous, mais nous sommes toujours sur jsdom 7 en interne.

Je vais fermer ceci, car je pense que la façon de faire Object.defineProperty est bonne. Si cela ne fonctionne pas pour vous dans jsdom 8, je serai heureux de le rouvrir.

Cool, merci je vais essayer ça.

@cpojer , je n'arrive pas à comprendre sur quoi je dois cliquer pour rouvrir ce problème...

Y a-t-il de toute façon dans l'environnement jest pour appeler jsdom.changeUrl(window, url) comme décrit ici https://github.com/tmpvar/jsdom#changing -the-url-of-an-existing-jsdom- instance de fenêtre dans [email protected] ?

ancien ticket, mais pour ceux qui ont encore ce problème, nous avons commencé à utiliser window.location.assign() la place, donc dans nos tests, nous pouvons nous moquer de la fonction assign comme ça.

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

Merci @th3fallen . C'est super!

Au fait @cpojer je commence sur FB le 1er mai.... ;P

Agréable!

J'essaie de migrer nos tests de Mocha+Chai+Sinon.js vers Jest et je n'arrive pas à comprendre comment changer d'emplacement pour un test particulier.
Jest 19.x utilise JSDom 9.12 qui ne permet pas de changer d'emplacement en utilisant l'astuce Object.defineProperty . De plus, je ne peux pas utiliser jsdom.changeURL() pour les raisons décrites dans tmpvar/jsdom#1700.
@cpojer qu'en est-il de l'implémentation d'une méthode de proxy pour jsdom.changeURL() dans Jest ?

@okovpashko nous prévoyons d'exposer jsdom à l'environnement : https://github.com/facebook/jest/issues/2460

Object.defineProperty travaille pour nous sur FB.

@thymikee J'ai vu ce problème mais j'ai pensé que la proposition avait été rejetée.
@cpojer J'ai mal lu votre exemple et l'ai mélangé avec d'autres liés à ce problème, où les gens ont suggéré d'utiliser Object.defineProperty(window, 'location', {value: 'url'}); . Merci!

J'ai besoin de changer non seulement le href, j'ai donc écrit une méthode simple, qui peut être utile à quelqu'un qui lira ce fil :

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

Toutes mes excuses pour faire traîner ce fil plus loin, mais j'ai essayé de me moquer de la fonction push comme suggéré ...

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

mais je reçois toujours une erreur jsdom que je n'arrive pas à contourner:

       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

Je me rends compte qu'il s'agit d'une erreur jsdom, mais pour ceux qui ont résolu ce problème, y a-t-il un autre contexte de configuration que vous pourriez partager et qui pourrait me permettre de contourner ce problème ?

Merci

@matt-dalton essayez ma suggestion dans https://github.com/facebook/jest/issues/890#issuecomment -295939071 fonctionne bien pour moi

@matt-dalton quelle est votre URL ? avez-vous testURL défini dans votre jest-config.json ou s'initialise-t-il en tant que about:blank ?

@ianlyons Ouais, j'ai défini la valeur de "https://test.com/" pour cela dans le package.json, et aucun des chemins n'apparaît comme blank .

@th3fallen Si je vous comprends bien, je ne pense pas que cela fonctionne pour mon cas d'utilisation. Transmettez-vous l'URL en tant que valeur de contexte qui déclenche l'attribution ? J'essaie de mettre en place un test d'intégration rudimentaire, donc je veux vérifier comment le routeur répond au chargement de données initial. J'ai moqué la réponse de l'API, puis j'ai besoin que le changement d'URL ait lieu en utilisant la logique de l'application (c'est-à-dire que je ne veux pas le déclencher moi-même en externe).

Object.defineProperty semble faire l'affaire pour tester les fonctionnalités qui reposent sur window.location.search , par exemple. Cela étant dit, il mute window.location.search donc d'autres tests peuvent être impactés. Existe-t-il un moyen "d'annuler" les modifications que vous avez apportées à window.location.search via Object.defineProperty , un peu comme les fonctions de plaisanterie ont la fonction mockReset ?

@msholty-fd, vous pouvez essayer cette approche :

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

Il a cessé de fonctionner dans Jest 22.0.1

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

Message d'erreur:

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

Hmm, nous pourrions avoir besoin d'une manière ou d'une autre d'autoriser les gens à appeler reconfigure . https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring-the-jsdom-with-reconfiguresettings

A ouvert un nouveau problème lié à cela, puisque celui-ci a été fermé : #5124

@SimenB Je ne suis pas convaincu que Jest devrait résoudre ce problème. JSDOM devrait permettre window.location.assign() de fonctionner comme prévu et de reconfigurer la sortie de window.location.href etc.

J'ai obtenu TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL parce que jsdom a l'URL de base par défaut à about:blank

J'ai essayé d'assigner une URL de base à jsdom , j'y ai passé 4 heures sans succès (je sais comment faire, il suffit d'insérer <base href='your_base_url' /> dans le dom; mais le dom est créé par jest , pas par moi, alors j'ai abandonné.

la solution Object.defineProperty ne fonctionne qu'avec l'ancienne version de jsdom (vous obtenez une erreur de propriété 'impossible de redéfinir la propriété avec la version ultérieure de jsdom );
si vous utilisez jsdom ver > 10, comme @th3fallen mentionné est la bonne solution.
utiliser window.location.assign est la bonne façon de faire

Si vous voulez juste une autre URL que about:blank , vous pouvez utiliser testURL config.

merci @SimenB pour votre réponse.

Non, je parlais de base url pas url . J'ai un code qui fera window.location.href="/login" et lors de l'exécution jest , jsdom jette une exception se plaignant /login n'est pas une URL valide

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

J'ai vérifié le code source de jsdom et j'ai réalisé que c'était parce que je n'avais pas de configuration d'URL de base (cela équivaut à taper "/login" dans la barre d'URL du navigateur sans adresse de base).

avec jsdom , normalement nous pouvons configurer l'URL de base via

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

mais parce que jest a configuré jsdom , c'est hors de notre contrôle.
--- mise à jour : je suppose que je peux explicitement ajouter jsdom comme dépendance et configurer jsdom manuellement.

J'ai ensuite trouvé une solution qui consiste à remplacer window.location.href= par window.location.assign et à simuler la fonction assign et cela a fonctionné pour moi

@bochen2014 ce numéro contient plus d'informations sur l'utilisation de la nouvelle version de jsdom : #5124

tl;dr : vous pouvez vous moquer window.location.assign() , ou vous pouvez utiliser le jest-environment-jsdom-global , ce qui vous permettra de reconfigurer jsdom en vol.

merci @simon360

C'est ce que j'ai fait ;-)
J'ai utilisé jsdom.reconfigure pour configurer différents urls initiaux dans mes tests, et chaque fois que j'ai besoin de changer d'URL dans le code (pas de test), j'utilise window.location.assign et je me moque de lui. qui a fonctionné pour moi.

juste pour les personnes qui peuvent/vont rencontrer le même problème, pour définir l'URL de votre jsdom

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

notez que cela définira l'URL pour tous vos tests ;
si vous voulez une URL différente dans certains tests particuliers, utilisez jsdom.reconfigure api ;
si vous avez besoin de changer l'URL à la volée en dehors du code de test unitaire (c'est-à-dire le code de production), vous devez utiliser window.location.assign et le simuler.

Je l'ai posté sur un autre ticket, mais je le posterai ici :

Trouvé une bonne solution pour Jest 21.2.1

Ok, jusqu'à présent, la solution la plus simple autour de cela est:
Allez dans vos paramètres Jest (par exemple, j'utiliserai package.json):

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

Vous aurez maintenant accès à l'objet window et vous pourrez ensuite définir l'URL sur ce que vous voulez pendant les tests.

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

J'espère que cela aide quelqu'un.

@ petar-prog91 qui a été utile. Vous avez une faute de frappe cependant - il devrait être testURL non TestURL

@BarthesSimpson merci pour l'avis, commentaire mis à jour.

Arrêtez de poster ça, ça ne marche pas sur la plaisanterie": "^22.4.2"

Salut,
J'ai utilisé ceci dans le test, je supprime l'état global et en crée un nouveau avec 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 confirme - cela donne TypeError: Cannot redefine property: href

@annemarie35 ne fonctionne pas - ReferenceError: JSDOM is not defined

Je ne sais pas si cela aiderait quelqu'un, mais c'est ce que je fais actuellement.

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

Écrivez une fonction de redirection et utilisez-la à la place. Ainsi, lors du test d'env, il s'appuiera sur jsdom.reconfigure url pour modifier la partie de l'url.

je l'utilise comme ça

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

Après ça, dans ton test, ça peut être qc comme ça

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

J'ai fini par faire ceci qui a fonctionné:

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

J'ai ceci en haut de mon fichier d'installation 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;

Corrigé en définissant "testURL": " http://localhost/ " dans la configuration Jest (j'utilise la dernière version). Par défaut, c'est " about:blank " et cela provoquait une erreur JSDOM (vous ne pouvez pas changer l'url "about:blank" en autre chose).

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

J'ai trouvé cet article très utile : https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Dans votre configuration Jest, assurez-vous de définir les éléments suivants :

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

Ensuite, dans votre section beforeEach() pour votre test, modifiez le chemin selon vos besoins en utilisant
history.pushState().

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

Voila ! Maintenant, vous modifiez votre chemin pour n'importe quel test, sans avoir à remplacer les configurations jsdom comme d'autres le suggèrent dans le fil mentionné ci-dessus. Je ne sais pas sur quel fil j'ai trouvé cette solution, mais félicitations au développeur qui l'a postée !"

@Mike-Tran Tu déchires ! Cela a totalement fonctionné, si simple. Je n'ai même pas eu à utiliser le paramètre testURL.

@Mike-Tran Ça marche ! Merci! Cependant, je n'avais pas besoin du testURL ou du beforeEach . Je viens de faire:

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

Et maintenant je n'ai plus besoin d'utiliser Object.defineProperty 😅

@jcmcneal merci qui l'a fait pour moi! (plaisanterie 23.0.0)

Si votre objectif est de vous moquer de l'objet window , voici ma solution (pas si élégante, mais ça marche):

Créez une classe d'interface (vous ne savez pas si interface est le bon mot, mais j'espère que vous avez compris) :

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

Dans votre code actuel, remplacez window par la méthode d'interface, par exemple win

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

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

Ensuite, dans vos tests de plaisanterie, vous vous moquez d'eux pour que jsdom ne se plaigne pas. Vous pouvez même les affirmer :

// 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 merci ! Tout fonctionne comme aspecté!

la classe SSOtestComponent étend 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 ;

j'écris le cas de test unitaire en utilisant enjyme et jest comment écrira la condition window.location ... pls donne la réponse

cela a fonctionné pour moi

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

sur plaisanterie version 23.6.0

Cela a fonctionné pour moi.

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

@FelipeBohnertPaetzold merci

Merci @FelipeBohnertPaetzold. J'utilisais location.host dans mon code, j'ai donc trouvé que j'avais besoin d'un objet de localisation complet , donc ce qui suit a mieux fonctionné pour moi, plutôt que d'avoir à passer manuellement chaque propriété de localisation :

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

Notez que cela fonctionne dans Node 6.13+ (voir URL class docs ) et j'utilisais Jest 24.

Notez également que cela ne fonctionne pas avec les URL relatives, voir https://url.spec.whatwg.org/#example -url-parsing.

Ce TypeScript fonctionne pour moi sur Jest 24.0.0 et 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

Hé, mec) Super utilité !

Pour moi, les tests dans src/setupTests.test.ts sont un peu redondants, car vous avez déjà complètement testé mockWindow util dans src/testUtils.test.ts. Ainsi, dans les tests pour src/setupTests.ts , il suffit de tester que vous appelez mockWindow avec des arguments corrects.

Merci)

@tzvipm @jup-iter merci pour le 👍. Je viens de sortir @jedmao/storage et @jedmao/location , qui sont tous deux totalement indépendants de Jest. Vous devriez pouvoir utiliser spyOn les méthodes appropriées sans écrire de tests supplémentaires, car les packages npm sont entièrement testés.

Si vous obtenez cette erreur lors de l'utilisation de Vue, utilisez simplement this.$router.push({...}) au lieu de this.$router.go({...})

image

Placez le code ci-dessous sur la ligne 1 :
delete global.window.location;
global.window.location = "";

Un événement de clic qui modifie la window.location peut maintenant être capturé.

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

Cela marche:

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 mieux encore...

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

Vous avez raison, il s'agit bien d'un problème jsdom. Chez Facebook, ce que nous avons fait pour contourner ce problème, c'est d'utiliser ceci :

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

cela fonctionne pour nous, mais nous sommes toujours sur jsdom 7 en interne.

Je vais fermer ceci, car je pense que la façon de faire Object.defineProperty est bonne. Si cela ne fonctionne pas pour vous dans jsdom 8, je serai heureux de le rouvrir.

Ouais, j'ai quelques fonctions traitant de location.search et location.hash , et je veux le tester avec defineProperty comme vous l'avez mentionné. Cela ne fonctionnera pas !

Lorsque j'ai désactivé le mode silencieux jest , j'ai trouvé ceci : 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

Et maintenant, je n'ai aucune idée de la façon de tester mes fonctions.

N'importe qui a un moyen de changer le test url

Vous avez raison, il s'agit bien d'un problème jsdom. Chez Facebook, ce que nous avons fait pour contourner ce problème, c'est d'utiliser ceci :

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

cela fonctionne pour nous, mais nous sommes toujours sur jsdom 7 en interne.
Je vais fermer ceci, car je pense que la façon de faire Object.defineProperty est bonne. Si cela ne fonctionne pas pour vous dans jsdom 8, je serai heureux de le rouvrir.

Ouais, j'ai quelques fonctions traitant de location.search et location.hash , et je veux le tester avec defineProperty comme vous l'avez mentionné. Cela ne fonctionnera pas !

Lorsque j'ai désactivé le mode silencieux jest , j'ai trouvé ceci : 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

Et maintenant, je n'ai aucune idée de la façon de tester mes fonctions.

N'importe qui a un moyen de changer le test url

Et dans ma situation, avoir un champ testURL dans le fichier jest.config.js peut fonctionner. Mais que se passe-t-il si je veux changer testURL avant chaque test.

J'ai trouvé cet article très utile : https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Dans votre configuration Jest, assurez-vous de définir les éléments suivants :

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

Ensuite, dans votre section beforeEach() pour votre test, modifiez le chemin selon vos besoins en utilisant
history.pushState().

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

Voila ! Maintenant, vous modifiez votre chemin pour n'importe quel test, sans avoir à remplacer les configurations jsdom comme d'autres le suggèrent dans le fil mentionné ci-dessus. Je ne sais pas sur quel fil j'ai trouvé cette solution, mais félicitations au développeur qui l'a postée !"


Excellente résolution !!! Merci beaucoup! @Mike-Tran
Je voulais une solution courte et non invasive comme celle-ci !

Pour que cela fonctionne à partir de juin 2019, je devais faire ceci:

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

J'utilise ça....

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

Une partie de mon JSDOMTestWrapper peut probablement aider quelqu'un

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

Cette approche fonctionne depuis le 27 septembre 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
});

Une autre solution fonctionne pour moi actuellement sans écrire jsdom :

  1. Assurez-vous d'avoir défini testURL dans jest.config.js , quelle que soit la valeur :
// jest.config.js
'testURL': 'https://someurl.com'

Dans votre fichier de test :

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

Tiré de : https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Merci à Ryan !

ne fonctionne pas pour moi :

TypeError : affectation aux propriétés en lecture seule non autorisée en mode strict

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

ne fonctionne pas pour moi :

TypeError : affectation aux propriétés en lecture seule non autorisée en mode strict

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

ajoutez ceci au fichier global.

supprimer global.window.location ;
global.window.location = "" ;

Cette approche fonctionne depuis le 27 septembre 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
});

J'essaie quelque chose de similaire, avec location.assign, mais il semble que cela ne fonctionne plus.

cela fonctionne pour moi sur plaisanterie 24.9.0

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

cela fonctionne pour moi sur plaisanterie 24.9.0

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

J'ai dû rendre le code asynchrone pour que cela fonctionne parce que j'exécutais du code dans une promesse.

donc ça marche maintenant 😃

Comment tester l'emplacement de changement dans l'action 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/');

j'ai erreur:

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

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

cela a fonctionné pour moi

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

sur plaisanterie version 23.6.0

c'est quoi le global ?

où est la définition globale?

Cela a fonctionné pour moi.

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

cela crée un emplacement avec toutes les fonctionnalités d'origine, mais c'est simulable :

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
Cette page vous a été utile?
0 / 5 - 0 notes