Jest: window.location.href нельзя изменить в тестах.

Созданный на 13 апр. 2016  ·  70Комментарии  ·  Источник: facebook/jest

Привет @cpojer ,

На самом деле это скорее проблема jsdom@8 ... см. tmpvar/jsdom#1388, но я также хочу закрепить здесь, чтобы Jest подбирал любое решение, которое предлагает jsdom.

Раньше с помощью [email protected]/[email protected] вы могли написать такой тест:

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

И этот тест пройдет. Поскольку jsdom@8 это больше невозможно, и эти тесты не пройдены.

Похоже, что jsdom ищет какую-то возможность, просто хотел убедиться, что Jest воспользуется этой возможностью, когда она будет доступна.

Самый полезный комментарий

Вы правы, это действительно проблема jsdom. В Facebook, чтобы обойти это, мы использовали это:

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

это работает для нас, однако внутри мы все еще используем jsdom 7.

Я закрою это, так как я считаю, что способ делать вещи Object.defineProperty в порядке. Если это не работает для вас в jsdom 8, я буду рад снова открыть его.

Все 70 Комментарий

Вы правы, это действительно проблема jsdom. В Facebook, чтобы обойти это, мы использовали это:

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

это работает для нас, однако внутри мы все еще используем jsdom 7.

Я закрою это, так как я считаю, что способ делать вещи Object.defineProperty в порядке. Если это не работает для вас в jsdom 8, я буду рад снова открыть его.

Круто, спасибо, я попробую это.

@cpojer , я не могу понять, что мне нужно нажать, чтобы снова открыть эту проблему...

В любом случае, в среде jest можно вызвать jsdom.changeUrl(window, url) , как описано здесь https://github.com/tmpvar/jsdom#change-the-url-of-an-existing-jsdom- экземпляр окна в [email protected] ?

старый билет, но для тех, у кого все еще есть эта проблема, мы начали использовать вместо этого window.location.assign() , поэтому в наших тестах мы можем издеваться над функцией assign вот так..

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

Спасибо @th3fallen . Это классно!

Кстати , @cpojer я начинаю в FB 1 мая.... ;P

Хороший!

Я пытаюсь перенести наши тесты с Mocha+Chai+Sinon.js на Jest и не могу понять, как изменить местоположение для конкретного теста.
Jest 19.x использует JSDom 9.12 , который не позволяет менять местоположение с помощью трюка Object.defineProperty . Также я не могу использовать jsdom.changeURL() по причинам, описанным в tmpvar/jsdom#1700.
@cpojer как насчет реализации какого-то прокси-метода для jsdom.changeURL() в Jest?

@okovpashko мы планируем выставить jsdom в среду: https://github.com/facebook/jest/issues/2460

Object.defineProperty работает у нас в FB.

@thymikee Я видел эту проблему, но думал, что предложение было отклонено.
@cpojer Я неправильно прочитал ваш пример и перепутал его с другими, связанными с этой проблемой, где люди предлагали использовать Object.defineProperty(window, 'location', {value: 'url'}); . Спасибо!

Мне нужно изменить не только href, поэтому я написал простой метод, который может быть полезен для тех, кто будет читать эту тему:

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

Извиняюсь за дальнейшее затягивание этой темы, но я попытался смоделировать функцию push, как было предложено...

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

но я все еще получаю ошибку jsdom, которую я не могу обойти:

       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

Я понимаю, что это ошибка jsdom, но для тех, кто решил эту проблему, есть ли еще контекст настройки, которым вы могли бы поделиться, что могло бы позволить мне обойти это?

Спасибо

@matt-dalton попробуйте мое предложение в https://github.com/facebook/jest/issues/890#issuecomment -295939071, у меня хорошо работает

@matt-dalton какой у тебя URL? у вас есть testURL, установленный в вашем jest-config.json , или он инициализируется как about:blank ?

@ianlyons Да, я установил для этого значение "https://test.com/" в package.json, и ни один из путей не отображается как blank .

@th3fallen th3fallen Если я правильно вас понял, я не думаю, что это работает для моего варианта использования. Вы передаете URL-адрес как значение контекста, которое вызывает запуск assign? Я пытаюсь собрать элементарный интеграционный тест, поэтому хочу проверить, как маршрутизатор реагирует на начальную загрузку данных. Я издевался над ответом API, а затем мне нужно, чтобы изменение URL-адреса происходило с использованием логики приложения (т.е. я не хочу запускать его извне самостоятельно).

Например, Object.defineProperty , кажется, помогает тестировать функциональность, основанную на window.location.search . При этом он мутирует window.location.search , поэтому другие тесты могут быть затронуты. Есть ли способ «отменить» изменения, которые вы сделали на window.location.search через Object.defineProperty , вроде того, как функции шутливого макета имеют функцию mockReset ?

@msholty-fd, вы можете попробовать этот подход:

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

Он перестал работать в Jest 22.0.1

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

Сообщение об ошибке:

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

Хм, нам может понадобиться как-то разрешить людям звонить reconfigure . https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring-the-jsdom-with-reconfiguresettings

Открыл новый вопрос, связанный с этим, так как этот был закрыт: # 5124

@SimenB Я не уверен, что Jest должен это исправить. JSDOM должен позволить window.location.assign() работать по назначению и перенастроить вывод window.location.href и т. д.

Я получил TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL , потому что jsdom имеет базовый URL-адрес по умолчанию about:blank

Я попытался назначить базовый URL-адрес jsdom , безуспешно потратил на это 4 часа (я знаю, как это сделать, просто вставьте <base href='your_base_url' /> в дом, но дом создается jest , не мной, поэтому я сдался.

решение Object.defineProperty работает только со старой версией jsdom (вы получаете ошибку «невозможно переопределить свойство с более поздней версией jsdom »);
если вы используете jsdom ver> 10, как упоминал @th3fallen , это правильное решение.
используйте window.location.assign это правильный путь

Если вам просто нужен другой URL, отличный от about:blank , вы можете использовать конфигурацию testURL .

спасибо @SimenB за ваш ответ.

Нет, я говорил о base url , а не о url . У меня есть код, который будет делать window.location.href="/login" , и при запуске jest jsdom генерирует исключение, жалуясь, что /login не является допустимым URL-адресом

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

Я проверил исходный код jsdom и понял, что это связано с тем, что у меня нет настройки базового URL-адреса (это эквивалентно вводу «/login» в строке URL-адреса браузера без базового адреса).

с jsdom , обычно мы можем настроить базовый URL-адрес через

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

но поскольку jest установили jsdom , это вне нашего контроля.
--- обновление: я полагаю, что могу явно добавить jsdom в качестве зависимости и настроить jsdom вручную.

Затем я нашел решение, которое состоит в том, чтобы заменить window.location.href= на window.location.assign и смоделировать функцию assign , и это сработало для меня.

@bochen2014 в этом выпуске содержится дополнительная информация о том, как использовать новую версию jsdom: #5124.

Вкратце: вы можете издеваться над window.location.assign() или использовать jest-environment-jsdom-global , что позволит вам перенастроить jsdom в полете.

спасибо @simon360

я так и сделал ;-)
Я использовал jsdom.reconfigure для настройки разных начальных urls в своих тестах, и всякий раз, когда мне нужно изменить URL-адрес в коде (не в тесте), я использую window.location.assign и издевался над ним. который работал для меня.

только для людей, которые могут столкнуться с той же проблемой, чтобы установить URL-адрес для вашего jsdom

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

обратите внимание, что это установит URL для всех ваших тестов;
если вам нужен другой URL-адрес в некоторых конкретных тестах, используйте API jsdom.reconfigure ;
если вам нужно изменить URL-адрес на лету за пределами кода модульного теста (т.е. производственного кода), вам нужно использовать window.location.assign и издеваться над ним.

Выложил в другой тикет, но выложу сюда:

Нашел хорошее решение для Jest 21.2.1

Хорошо, пока самое простое решение:
Перейдите в настройки Jest (например, я буду использовать package.json):

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

Теперь у вас будет доступ к объекту окна, а затем вы можете установить любой URL-адрес во время тестов.

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

Надеюсь, это кому-то поможет.

@petar-prog91, это было полезно. У вас опечатка - должно быть testURL , а не TestURL

@BarthesSimpson , спасибо за уведомление, обновленный комментарий.

Прекратите публиковать это, это не работает в шутку": "^22.4.2"

Привет,
Я использовал это в тесте, я удаляю глобальное состояние и создаю новое с помощью 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 подтверждает — выдает TypeError: Cannot redefine property: href

@annemarie35 не работает — ReferenceError: JSDOM is not defined

Я не знаю, поможет ли это кому-то, но это то, что я сейчас делаю.

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

Напишите функцию перенаправления и используйте ее вместо этого. Таким образом, при тестировании env он будет полагаться на URL-адрес jsdom.reconfigure для изменения части URL-адреса.

Я использую это так

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

После этого в вашем тесте может быть что-то вроде этого

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

В итоге я сделал это, что сработало:

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

У меня есть это в верхней части моего установочного файла 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;

Исправлено, установив «testURL»: « http://localhost/ » в конфигурации Jest (я использую последнюю версию). По умолчанию это « about:blank », и это вызывало ошибку JSDOM (вы не можете изменить URL-адрес «about:blank» на что-то другое).

Ресурсы:
http://jestjs.io/docs/en/configuration#testurl -строка
https://github.com/jsdom/jsdom/issues/1372

Я нашел этот пост очень полезным: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

«В конфигурации Jest обязательно установите следующее:

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

Затем в разделе beforeEach() для вашего теста измените путь по мере необходимости, используя
история.pushState().

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

Вуаля! Теперь вы меняете свой путь для любого теста, не переопределяя какие-либо конфигурации jsdom, как это предлагают другие в теме, упомянутой выше. Не уверен, в какой теме я нашел это решение, но спасибо разработчику, который его опубликовал!»

@Mike-Tran Ты молодец! Это полностью сработало, так просто. Мне даже не пришлось использовать настройку testURL.

@ Майк-Тран Это работает! Спасибо! Однако мне не нужны были testURL или beforeEach . Я только что сделал:

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

И теперь мне больше не нужно использовать Object.defineProperty 😅

@jcmcneal спасибо, что сделали это для меня! (шутка 23.0.0)

Если ваша цель — издеваться над объектом window , вот мое (не очень элегантное, но работающее) решение:

Создайте класс интерфейса (не уверен, что интерфейс — это правильное слово, но я надеюсь, что вы поняли суть):

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

В вашем фактическом коде замените window методом интерфейса, например, win

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

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

Затем в своих шутливых тестах вы просто издеваетесь над ними, чтобы jsdom не жаловался. Вы даже можете утверждать их:

// 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 спасибо! Все работает как аспектно!

класс SSOtestComponent расширяет 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;

я пишу пример модульного теста, используя enjyme и jest, как написать условие window.location ... пожалуйста, дайте ответ

это сработало для меня

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

на шутке версии 23.6.0

Это сработало для меня.

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

@FelipeBohnertPaetzold спасибо

Спасибо @FelipeBohnertPaetzold. Я использовал location.host в своем коде, поэтому обнаружил, что мне нужен полный объект местоположения, поэтому для меня лучше работало следующее, вместо того, чтобы вручную передавать каждое свойство местоположения:

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

Обратите внимание, это работает в Node 6.13+ (см. документацию класса URL ), и я использовал Jest 24.

Также обратите внимание, что это не работает с относительными URL-адресами, см. https://url.spec.whatwg.org/#example -url-parsing.

Этот TypeScript работает у меня на Jest 24.0.0 и Node 10.15.0 :

источник/setupTests.ts

import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');

источник/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();
        });
    });
});

источник/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;
    }
}

источник/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

Привет, чувак) Отличная утилита!

Для меня тесты в src/setupTests.test.ts немного избыточны, потому что вы уже полностью протестировали утилиту mockWindow в src/testUtils.test.ts. Итак, в тестах для src/setupTests.ts достаточно проверить, что вы вызываете mockWindow с правильными аргументами.

Спасибо)

@tzvipm @jup-iter спасибо за 👍. Я только что выпустил @jedmao/storage и @jedmao/location , которые совершенно не зависят от Jest. У вас должна быть возможность spyOn соответствующих методов без написания каких-либо дополнительных тестов, поскольку пакеты npm полностью протестированы.

Если вы получаете эту ошибку при использовании Vue, просто используйте this.$router.push({...}) вместо this.$router.go({...})

image

Поместите код ниже в строку 1:
delete global.window.location;
global.window.location = "";

Событие щелчка, которое меняет window.location, теперь можно зафиксировать.

"шутка": "^23.6.0"
v10.15.0
6.5.0

Это работает:

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

Или еще лучше...

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

Вы правы, это действительно проблема jsdom. В Facebook, чтобы обойти это, мы использовали это:

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

это работает для нас, однако внутри мы все еще используем jsdom 7.

Я закрою это, так как я считаю, что способ делать вещи Object.defineProperty в порядке. Если это не работает для вас в jsdom 8, я буду рад снова открыть его.

Да, у меня есть некоторые функции, работающие с location.search и location.hash , и я хочу протестировать их с defineProperty , как вы упомянули. Это не сработает!

Когда я отключил беззвучный режим jest , я обнаружил это: 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

И теперь я понятия не имею, как проверить свои функции.

Кто-нибудь может изменить тест url

Вы правы, это действительно проблема jsdom. В Facebook, чтобы обойти это, мы использовали это:

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

это работает для нас, однако внутри мы все еще используем jsdom 7.
Я закрою это, так как я считаю, что способ делать вещи Object.defineProperty в порядке. Если это не работает для вас в jsdom 8, я буду рад снова открыть его.

Да, у меня есть некоторые функции, работающие с location.search и location.hash , и я хочу протестировать их с defineProperty , как вы упомянули. Это не сработает!

Когда я отключил беззвучный режим jest , я обнаружил это: 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

И теперь я понятия не имею, как проверить свои функции.

Кто-нибудь может изменить тест url

И в моей ситуации может работать поле testURL в файле jest.config.js . Но что, если я хочу изменить testURL перед каждым тестом.

Я нашел этот пост очень полезным: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

«В конфигурации Jest обязательно установите следующее:

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

Затем в разделе beforeEach() для вашего теста измените путь по мере необходимости, используя
история.pushState().

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

Вуаля! Теперь вы меняете свой путь для любого теста, не переопределяя какие-либо конфигурации jsdom, как это предлагают другие в теме, упомянутой выше. Не уверен, в какой теме я нашел это решение, но спасибо разработчику, который его опубликовал!»


Отличное решение!!! Большое тебе спасибо! @Майк-Тран
Я хотел короткое и неинвазивное решение, подобное этому!

Чтобы заставить это работать по состоянию на июнь 2019 года, мне пришлось сделать следующее:

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

Я использую это ....

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

Возможно, часть моего JSDOMTestWrapper может кому-то помочь

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

Этот подход работает с 27 сентября 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
});

В настоящее время у меня работает другое решение без написания jsdom :

  1. Убедитесь, что вы установили testURL в jest.config.js независимо от значения:
// jest.config.js
'testURL': 'https://someurl.com'

В вашем тестовом файле:

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

Узнал из: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Спасибо Райану!

у меня не работает:

TypeError: назначение свойств только для чтения не разрешено в строгом режиме

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

у меня не работает:

TypeError: назначение свойств только для чтения не разрешено в строгом режиме

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

добавьте это в глобальный файл.

удалить global.window.location;
global.window.location = "";

Этот подход работает с 27 сентября 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
});

Я пытаюсь что-то подобное с location.assign, но, похоже, это больше не работает.

это работает для меня на шутке 24.9.0

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

это работает для меня на шутке 24.9.0

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

Мне пришлось сделать код асинхронным, чтобы заставить его работать, потому что я запускал код внутри промиса.

так сейчас работает 😃

Как проверить местоположение смены в действии 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/');

у меня ошибка:

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

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

это сработало для меня

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

на шутке версии 23.6.0

что глобальное?

где глобальное определение?

Это сработало для меня.

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

это создает Location со всеми исходными функциями, но его можно издеваться:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
Была ли эта страница полезной?
0 / 5 - 0 рейтинги