Jest: window.location.href kann in Tests nicht geändert werden.

Erstellt am 13. Apr. 2016  ·  70Kommentare  ·  Quelle: facebook/jest

Hallo @cpojer ,

Dies ist eigentlich eher ein jsdom@8 -Problem ... siehe tmpvar/jsdom#1388, aber ich möchte auch hier anheften, damit Jest jede Lösung aufgreift, die jsdom anbietet.

Bisher konnten Sie mit [email protected]/[email protected] einen Test wie diesen schreiben:

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

Und dieser Test würde bestehen. Seit jsdom@8 ist dies nicht mehr möglich und diese Tests schlagen fehl.

Scheint, als ob jsdom nach einer Art von Fähigkeit sucht, wollte nur sicherstellen, dass Jest diese Fähigkeit aufgreift, wenn sie verfügbar ist.

Hilfreichster Kommentar

Sie haben Recht, dies ist in der Tat ein jsdom-Problem. Bei Facebook haben wir Folgendes getan, um dies zu umgehen:

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

das funktioniert für uns, aber wir sind intern immer noch auf jsdom 7.

Ich werde dies schließen, da ich glaube, dass die Object.defineProperty Art, Dinge zu tun, in Ordnung ist. Wenn das in jsdom 8 für Sie nicht funktioniert, öffne ich es gerne wieder.

Alle 70 Kommentare

Sie haben Recht, dies ist in der Tat ein jsdom-Problem. Bei Facebook haben wir Folgendes getan, um dies zu umgehen:

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

das funktioniert für uns, aber wir sind intern immer noch auf jsdom 7.

Ich werde dies schließen, da ich glaube, dass die Object.defineProperty Art, Dinge zu tun, in Ordnung ist. Wenn das in jsdom 8 für Sie nicht funktioniert, öffne ich es gerne wieder.

Cool, danke, das werde ich ausprobieren.

@cpojer , ich kann anscheinend nicht herausfinden, worauf ich klicken muss, um dieses Problem erneut zu öffnen ...

Gibt es in der Umgebung von jest sowieso einen Aufruf jsdom.changeUrl(window, url) wie hier beschrieben https://github.com/tmpvar/jsdom#changing -the-url-of-an-existing-jsdom- Fensterinstanz in [email protected] ?

altes Ticket, aber für diejenigen, die dieses Problem immer noch haben, haben wir begonnen, stattdessen window.location.assign() zu verwenden, damit wir in unseren Tests die assign -Funktion so verspotten können.

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

Danke @th3fallen . Das ist cool!

Übrigens @cpojer Ich fange am 1.Mai bei FB an.... ;P

Schön!

Ich versuche, unsere Tests von Mocha+Chai+Sinon.js zu Jest zu migrieren und kann nicht herausfinden, wie ich den Speicherort für einen bestimmten Test ändern kann.
Jest 19.x verwendet JSDom 9.12 , das es nicht erlaubt, den Standort mit dem Object.defineProperty -Trick zu ändern. Außerdem kann ich jsdom.changeURL() aus den in tmpvar/jsdom#1700 beschriebenen Gründen nicht verwenden.
@cpojer , was ist mit der Implementierung einer Proxy-Methode für jsdom.changeURL() in Jest?

@okovpashko wir planen, jsdom der Umgebung auszusetzen: https://github.com/facebook/jest/issues/2460

Object.defineProperty arbeitet für uns bei FB.

@thymikee Ich habe dieses Problem gesehen, dachte aber, dass der Vorschlag abgelehnt wurde.
@cpojer Ich habe dein Beispiel falsch gelesen und es mit anderen im Zusammenhang mit diesem Problem verwechselt, wo Leute vorgeschlagen haben, Object.defineProperty(window, 'location', {value: 'url'}); zu verwenden. Danke schön!

Ich muss nicht nur die href ändern, also habe ich eine einfache Methode geschrieben, die für jemanden nützlich sein kann, der diesen Thread liest:

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

Entschuldigung, dass ich diesen Thread weiter in die Länge gezogen habe, aber ich habe versucht, die Push-Funktion wie vorgeschlagen zu verspotten ...

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

aber ich bekomme immer noch einen jsdom-Fehler, den ich anscheinend nicht umgehen kann:

       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

Mir ist klar, dass dies ein jsdom-Fehler ist, aber für diejenigen, die dies gelöst haben, gibt es einen weiteren Setup-Kontext, den Sie teilen könnten, damit ich das umgehen könnte?

Danke

@matt-dalton versuche meinen Vorschlag in https://github.com/facebook/jest/issues/890#issuecomment -295939071 funktioniert gut für mich

@matt-dalton wie lautet deine URL? Haben Sie testURL in Ihrem jest-config.json festgelegt oder wird es als about:blank initialisiert?

@ianlyons Ja, ich habe dafür in der package.json den Wert "https://test.com/" festgelegt, und keiner der Pfade wird als blank angezeigt.

@th3fallen Wenn ich Sie richtig verstehe, glaube ich nicht, dass dies für meinen Anwendungsfall funktioniert. Übergeben Sie die URL als Kontextwert, der bewirkt, dass die Zuweisung ausgelöst wird? Ich versuche, einen rudimentären Integrationstest zusammenzustellen, also möchte ich überprüfen, wie der Router auf die anfängliche Datenlast reagiert. Ich habe die API-Antwort verspottet und muss dann die URL-Änderung mithilfe der App-Logik durchführen (dh ich möchte sie nicht selbst extern auslösen).

Object.defineProperty scheint zum Beispiel zum Testen von Funktionen geeignet zu sein, die auf window.location.search angewiesen sind. Davon abgesehen mutiert es window.location.search , sodass andere Tests beeinflusst werden können. Gibt es eine Möglichkeit, die Änderungen, die Sie an window.location.search vorgenommen haben, über Object.defineProperty rückgängig zu machen, so als hätten Scheinfunktionen die Funktion mockReset ?

@msholty-fd Sie könnten diesen Ansatz versuchen:

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

Es funktionierte nicht mehr in Jest 22.0.1

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

Fehlermeldung:

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

Hmm, wir müssen den Leuten vielleicht irgendwie erlauben, reconfigure anzurufen. https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring -the-jsdom-with-reconfiguresettings

Habe diesbezüglich ein neues Thema aufgemacht, da dieses geschlossen wurde: #5124

@SimenB Ich bin nicht überzeugt, dass Jest das beheben sollte. JSDOM sollte zulassen, dass window.location.assign() wie beabsichtigt funktioniert, und die Ausgabe von window.location.href usw. neu konfigurieren.

Ich habe TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL bekommen, weil jsdom die Basis-URL standardmäßig auf about:blank hat

Ich habe versucht, jsdom eine Basis-URL zuzuweisen, habe 4 Stunden ohne Erfolg damit verbracht (ich weiß, wie es geht, füge einfach <base href='your_base_url' /> in den Dom ein; aber der Dom wird von jest , nicht von mir, also habe ich aufgegeben.

die Object.defineProperty -Lösung funktioniert nur mit der alten Version von $ jsdom (Sie erhalten einen 'cannot redefine property error with later version of jsdom ');
wenn Sie jsdom ver > 10 verwenden, ist wie von @th3fallen erwähnt die richtige Lösung.
Verwenden Sie window.location.assign ist der richtige Weg

Wenn Sie nur eine andere URL als about:blank wollen, können Sie die testURL -Konfiguration verwenden.

danke @SimenB für deine Antwort.

Nein, ich habe über base url gesprochen, nicht url . Ich habe Code, der window.location.href="/login" , und wenn jest ausgeführt wird, jsdom Ausnahme aus, die sich beschwert, dass /login keine gültige URL ist

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

Ich habe den Quellcode von jsdom überprüft und festgestellt, dass dies daran liegt, dass ich keine Basis-URL-Einrichtung habe (dies entspricht der Eingabe von „/login“ in der Browser-URL-Leiste ohne Basisadresse).

Mit jsdom können wir normalerweise die Basis-URL über einrichten

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

aber da jest jsdom eingerichtet hat, liegt es außerhalb unserer Kontrolle.
--- Update: Ich nehme an, ich kann jsdom explizit als Abhängigkeit hinzufügen und jsdom manuell konfigurieren.

Ich habe dann eine Lösung gefunden, die darin besteht, window.location.href= durch window.location.assign zu ersetzen und die Funktion assign zu simulieren, und es hat für mich funktioniert

@bochen2014 Diese Ausgabe enthält weitere Informationen zur Verwendung der neueren Version von jsdom: #5124

tl;dr: Sie können window.location.assign() verspotten oder jest-environment-jsdom-global verwenden, wodurch Sie jsdom im Flug neu konfigurieren können.

danke @simon360

das ist, was ich tat ;-)
Ich habe jsdom.reconfigure verwendet, um verschiedene anfängliche urls in meinen Tests einzurichten, und wann immer ich die URL im Code ändern muss (nicht testen), verwende ich window.location.assign und verspottete es. was bei mir funktioniert hat.

Nur für Leute, die möglicherweise auf das gleiche Problem stoßen / werden, um die URL für Ihre jsdom festzulegen

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

Beachten Sie, dass dies die URL für alle Ihre Tests festlegen wird.
Wenn Sie in bestimmten Tests eine andere URL wünschen, verwenden Sie jsdom.reconfigure api;
Wenn Sie die URL außerhalb des Komponententestcodes (dh des Produktionscodes) spontan ändern müssen, müssen Sie window.location.assign verwenden und es verspotten.

Ich habe es auf einem anderen Ticket gepostet, aber ich werde es hier posten:

Schöne Lösung für Jest 21.2.1 gefunden

Ok, bisher ist die einfachste Lösung:
Gehen Sie in Ihre Jest-Einstellungen (zum Beispiel verwende ich package.json):

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

Jetzt haben Sie Zugriff auf das Fensterobjekt und können die URL während der Tests beliebig einstellen.

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

Hoffentlich hilft das jemandem.

@petar-prog91 das war hilfreich. Sie haben jedoch einen Tippfehler - es sollte testURL sein, nicht TestURL

@BarthesSimpson danke für die Benachrichtigung, aktualisierter Kommentar.

Hör auf, das zu posten, es funktioniert im Scherz nicht": "^22.4.2"

Hallo,
Ich habe dies im Test verwendet, ich lösche den globalen Status und erstelle einen neuen mit 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 bestätigen — es gibt TypeError: Cannot redefine property: href

@annemarie35 funktioniert nicht — ReferenceError: JSDOM is not defined

Ich weiß nicht, ob das jemandem helfen würde, aber das ist, was ich gerade tue.

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

Schreiben Sie eine Umleitungsfunktion und verwenden Sie diese stattdessen. Beim Testen von env verlässt es sich also auf jsdom.reconfigure url, um den URL-Teil zu ändern.

Ich benutze es so

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

Danach kann es in Ihrem Test so sein

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

Am Ende habe ich das gemacht, was funktioniert hat:

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

Ich habe dies oben in meiner JSDOM-Setup-Datei:

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;

Es wurde behoben, indem "testURL": " http://localhost/ " in der Jest-Konfiguration eingestellt wurde (ich verwende die neueste Version). Standardmäßig ist es „ about:blank “ und verursachte einen JSDOM-Fehler (Sie können die „about:blank“-URL nicht in etwas anderes ändern).

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

Ich fand diesen Beitrag sehr hilfreich: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Stellen Sie in Ihrer Jest-Konfiguration sicher, dass Sie Folgendes einstellen:

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

Ändern Sie dann in Ihrem Abschnitt beforeEach() für Ihren Test den Pfad nach Bedarf, indem Sie verwenden
history.pushState().

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

Voila! Jetzt ändern Sie Ihren Pfad für jeden Test, ohne dass Sie jsdom-Konfigurationen überschreiben müssen, wie andere im oben genannten Thread vorschlagen. Ich bin mir nicht sicher, in welchem ​​Thread ich diese Lösung gefunden habe, aber ein großes Lob an den Entwickler, der sie gepostet hat!"

@Mike-Tran Du rockst! Das hat total funktioniert, so einfach. Ich musste nicht einmal die testURL-Einstellung verwenden.

@Mike-Tran Das funktioniert! Danke! Allerdings brauchte ich die testURL oder beforeEach nicht. Ich habe gerade:

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

Und jetzt muss ich nicht mehr Object.defineProperty verwenden 😅

@jcmcneal danke, das hat es für mich getan! (Scherz 23.0.0)

Wenn Ihr Ziel darin besteht, das window -Objekt zu verspotten, hier ist meine (nicht so elegante, aber funktionierende) Lösung:

Erstellen Sie eine Interface-Klasse (nicht sicher, ob Interface das richtige Wort ist, aber ich hoffe, Sie verstehen, worauf es ankommt):

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

Tauschen Sie in Ihrem eigentlichen Code window gegen die Methoden der Schnittstelle aus, zB win

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

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

Dann verspottest du sie in deinen Scherztests einfach, damit sich jsdom nicht beschwert. Sie können sie sogar behaupten:

// 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 danke! Alle Arbeiten wie vorgesehen!

Klasse SSOtestComponent erweitert 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;

Ich schreibe den Unit-Testfall mit enjyme und scherze, wie die Bedingung window.location geschrieben wird ... bitte geben Sie die Antwort

das hat bei mir funktioniert

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

auf jed Version 23.6.0

Das hat bei mir funktioniert.

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

@FelipeBohnertPaetzold danke

Danke @FelipeBohnertPaetzold. Ich habe location.host in meinem Code verwendet und festgestellt, dass ich ein vollständiges Standortobjekt benötigte, sodass Folgendes für mich besser funktionierte, als jede Standorteigenschaft manuell übergeben zu müssen:

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

Beachten Sie, dass dies in Node 6.13+ funktioniert (siehe URL-Klassendokumentation ) und ich Jest 24 verwendet habe.

Beachten Sie auch, dass dies nicht mit relativen URLs funktioniert, siehe https://url.spec.whatwg.org/#example -url-parsing.

Dieses TypeScript funktioniert für mich auf Jest 24.0.0 und 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

Hey, Mann) Großartig!

Für mich sind Tests in src/setupTests.test.ts etwas überflüssig, da Sie mockWindow util in src/testUtils.test.ts bereits vollständig getestet haben. Bei Tests für src/setupTests.ts reicht es also aus zu testen, dass Sie mockWindow mit korrekten Argumenten aufrufen.

Danke schön)

@tzvipm @jup-iter danke für das 👍. Ich habe gerade @jedmao/storage und @jedmao/location veröffentlicht, die beide völlig unabhängig von Jest sind. Sie sollten in der Lage sein, die geeigneten Methoden spyOn zu lernen, ohne zusätzliche Tests schreiben zu müssen, da die npm-Pakete vollständig getestet geliefert werden.

Falls Sie diesen Fehler bei der Verwendung von Vue erhalten, verwenden Sie einfach this.$router.push({...}) anstelle von this.$router.go({...})

image

Fügen Sie den folgenden Code in Zeile 1 ein:
delete global.window.location;
global.window.location = "";

Ein Klickereignis, das die window.location ändert, kann jetzt erfasst werden.

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

Das funktioniert:

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

Oder noch besser...

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

Sie haben Recht, dies ist in der Tat ein jsdom-Problem. Bei Facebook haben wir Folgendes getan, um dies zu umgehen:

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

das funktioniert für uns, aber wir sind intern immer noch auf jsdom 7.

Ich werde dies schließen, da ich glaube, dass die Object.defineProperty Art, Dinge zu tun, in Ordnung ist. Wenn das in jsdom 8 für Sie nicht funktioniert, öffne ich es gerne wieder.

Ja, ich habe einige Funktionen, die sich mit location.search und location.hash befassen, und ich möchte es mit defineProperty testen, wie Sie erwähnt haben. Es wird nicht funktionieren!

Als ich jest den stillen Modus ausschalte, habe ich Folgendes gefunden: 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

Und jetzt habe ich keine Ahnung, wie ich meine Funktionen testen soll.

Jeder hat eine Möglichkeit, den Test zu ändern url

Sie haben Recht, dies ist in der Tat ein jsdom-Problem. Bei Facebook haben wir Folgendes getan, um dies zu umgehen:

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

das funktioniert für uns, aber wir sind intern immer noch auf jsdom 7.
Ich werde dies schließen, da ich glaube, dass die Object.defineProperty Art, Dinge zu tun, in Ordnung ist. Wenn das in jsdom 8 für Sie nicht funktioniert, öffne ich es gerne wieder.

Ja, ich habe einige Funktionen, die sich mit location.search und location.hash befassen, und ich möchte es mit defineProperty testen, wie Sie erwähnt haben. Es wird nicht funktionieren!

Als ich jest den stillen Modus ausschalte, habe ich Folgendes gefunden: 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

Und jetzt habe ich keine Ahnung, wie ich meine Funktionen testen soll.

Jeder hat eine Möglichkeit, den Test zu ändern url

Und in meiner Situation kann ein Feld testURL in der Datei jest.config.js funktionieren. Aber was ist, wenn ich testURL vor jedem Test ändern möchte?

Ich fand diesen Beitrag sehr hilfreich: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"Stellen Sie in Ihrer Jest-Konfiguration sicher, dass Sie Folgendes einstellen:

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

Ändern Sie dann in Ihrem Abschnitt beforeEach() für Ihren Test den Pfad nach Bedarf, indem Sie verwenden
history.pushState().

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

Voila! Jetzt ändern Sie Ihren Pfad für jeden Test, ohne dass Sie jsdom-Konfigurationen überschreiben müssen, wie andere im oben genannten Thread vorschlagen. Ich bin mir nicht sicher, in welchem ​​Thread ich diese Lösung gefunden habe, aber ein großes Lob an den Entwickler, der sie gepostet hat!"


Hervorragende Lösung!!! Vielen Dank! @Mike-Tran
Ich wollte eine kurze und nicht invasive Lösung wie diese!

Damit dies ab Juni 2019 funktioniert, musste ich Folgendes tun:

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

Ich benutze das....

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

Wahrscheinlich kann ein Teil meines JSDOMTestWrapper jemandem helfen

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

Dieser Ansatz funktioniert seit dem 27. September 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
});

Eine andere Lösung funktioniert derzeit für mich, ohne jsdom zu schreiben:

  1. Stellen Sie sicher, dass Sie unabhängig vom Wert testURL in jest.config.js festgelegt haben:
// jest.config.js
'testURL': 'https://someurl.com'

In Ihrer Testdatei:

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

Gelernt von: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Danke an Ryan!

funktioniert bei mir nicht:

TypeError: Zuweisung zu schreibgeschützten Eigenschaften im strikten Modus nicht zulässig

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

funktioniert bei mir nicht:

TypeError: Zuweisung zu schreibgeschützten Eigenschaften im strikten Modus nicht zulässig

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

fügen Sie dies der globalen Datei hinzu.

global.window.location löschen;
global.window.location = "";

Dieser Ansatz funktioniert seit dem 27. September 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
});

Ich versuche etwas Ähnliches mit location.assign, aber das scheint nicht mehr zu funktionieren.

das funktioniert bei mir nur am 24.9.0

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

das funktioniert bei mir nur am 24.9.0

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

Ich musste den Code asynchron machen, damit dies funktioniert, weil ich Code innerhalb eines Versprechens ausführte.

also funktioniert jetzt 😃

Wie teste ich den Chenge-Standort in der Vuex-Aktion?

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

Ich habe Fehler:

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

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

das hat bei mir funktioniert

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

auf jed Version 23.6.0

was ist das globale?

Wo ist die globale Definition?

Das hat bei mir funktioniert.

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

Dadurch wird ein Standort mit allen ursprünglichen Funktionen erstellt, der jedoch verspottet werden kann:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen