Jsdom: 错误:未实现:导航

创建于 2018-01-12  ·  49评论  ·  资料来源: jsdom/jsdom

最近将jest (在后台使用 jsdom)从版本 21.2.0 升级到 22.0.6 后,我开始收到错误: "Error: Not implemented:" navigation

我的代码依赖于window.location并且我在测试中使用:

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

有没有办法使用新版本的 jsdom 定义window.location.search的值?

最有用的评论

请允许我发布我自己问题的答案😁
我只是将window.location = url;window.location.href = url;的用法替换为

window.location.assign(url);

然后在我的测试中我做了:

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

像魅力一样工作 - 希望它可以帮助别人👍

所有49条评论

我也收到这个错误

Error: Not implemented: navigation (except hash changes)
    at module.exports (...\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
    at navigateFetch (...\node_modules\jsdom\lib\jsdom\living\window\navigation.js:74:3)

jsdom 不支持导航,因此设置 window.location.href 或类似的将给出此消息。 我不确定 Jest 之前是否只是压制了这些消息,或者是什么。

这可能是您应该在测试中修复的问题,因为这意味着如果您在浏览器中运行这些测试,当您将页面导航到新 URL 时,测试运行器会完全被震撼,并且您将永远看不到任何测试结果。 在 jsdom 中,我们只是向控制台输出一条消息,您可以根据需要忽略该消息,或者您可以修复您的测试以使其在更多环境中更好地工作。

无论如何,我想为人们添加更多关于此的文档,因此我将保留此问题以跟踪这样做。

完全明白你在说什么。 最近的 Jest 22 更新从 JSDOM 9 到 11 IIRC,因此 9.x 中的行为可能完全不同。

除此之外,我希望看到在 JSDOM 中实现的导航带有某种标志,以使其在加载不同页面时无操作(与 HTML5 pushstate 的精神类似。)该库非常常用于测试目的因此,虽然可能是一个古怪的请求,但它会经常使用。

我认为我们不应该添加一个标志,使您的测试在 jsdom 中与在浏览器中运行的不同。 然后你的东西可能会在浏览器中被破坏(例如,它可能将用户重定向到其他页面,而不是执行测试看到的操作),你甚至不会注意到!

那么在这种情况下,除了不卸载当前页面上下文之外,它不会做任何不同的事情。 我仍然希望window.location.href被更新,等等。

@domenic我遇到了同样的问题,我想知道是否有某种最佳实践可以使用设置window.location的应用程序设置 JSDOM。 据我所知,JSDOM 在尝试设置window.location时会抛出错误,并在尝试设置window.location.href时记录错误 - 但是我在 mdn 上读到这两个应该是同义词。 我应该以另一种更容易存根的方式更新位置吗?
感谢帮助😅😅

请允许我发布我自己问题的答案😁
我只是将window.location = url;window.location.href = url;的用法替换为

window.location.assign(url);

然后在我的测试中我做了:

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

像魅力一样工作 - 希望它可以帮助别人👍

同意这应该是开箱即用的。 我们在 FB 模拟window.location ,但这与 jsdom 的History实现相冲突。

作为一个小团队,我们当然会感谢依赖我们在 jsdom 中正确实现导航的大型项目的帮助。

如果有人感兴趣, https://github.com/jsdom/jsdom/pull/1913可能是一个不错的起点。

可能的解决方案是在单元测试中依赖于window对象的依赖注入/模拟。

就像是:

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

这并不理想,但正如@domenic 所说

这可能是您应该在测试中修复的问题,因为这意味着如果您在浏览器中运行这些测试,那么当您将页面导航到新 URL 时,测试运行器会完全被震撼

现在我们接受这个,是的,我们更改了测试的实现代码,这被认为是不好的做法,但我们晚上也睡得很好!

快乐测试

@hontas的解决方案有帮助:

我确实在我的代码中使用了window.location.assign(Config.BASE_URL);

这是测试:

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

window.location.assign.mockClear();

同样的问题,我在我的代码中使用了window.location.search = foo; ,我想使用 jsdom(和 jest)来测试它🤔

PS:相关https://github.com/facebook/jest/issues/5266

更新到 jsdom 12.2.0 后出现错误:
类型错误:无法重新定义属性:分配
const assign = sinon.stub(document.location, 'assign')
如何解决?

@yuri-sakharov

更新到 jsdom 12.2.0 后出现错误:
类型错误:无法重新定义属性:分配
const assign = sinon.stub(document.location, 'assign')
如何解决?

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

需要是:

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

你需要用window替换document window

我有以下功能

export const isLocalHost = () => Boolean(
  window.location.hostname === 'localhost' ||
  // [::1] is the IPv6 localhost address.
  window.location.hostname === '[::1]' ||
  // 127.0.0.1/8 is considered localhost for IPv4.
  window.location.hostname.match(
    /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
  )
);

为了能够测试它是否有效,我直接注入了hostname

it('#isLocalHost should return true for all the cases of localhost', () => {
    window.location.hostname = 'localhost';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '[::1]';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '127.0.0.1';
    expect(isLocalHost()).toBeTruthy();

    // Reset back the hostname to avoid issues with it
    window.location.hostname = '';
  });

但我收到此错误。

我不希望jsdom完全实现导航,但至少添加键并模拟功能。

我对为什么不断收到此错误感到困惑,我只想能够设置该值。

@尼克哈尔夫
我像你写的那样将它替换为window.location但结果相同
TypeError: Cannot redefine property: assign
有任何想法吗?

与@yuri-sakharov 运行 mocha 的问题相同。

我似乎无法用window.location.*替换/更新/模拟或做任何事情。 我看到的唯一方法是创建我自己的自定义window.location模拟并更改整个代码库以依赖它。

@hontas的解决方案有帮助:

我确实在我的代码中使用了window.location.assign(Config.BASE_URL);

这是测试:

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

window.location.assign.mockClear();

@zxest@hontas解决方案的 Jest 版本对我不起作用,但这样做了:

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

如果您不想更改代码以使用location.assign - 这似乎适用于 JSDom 11 和 13(尽管 JSDom 将来可能会破坏它......)

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

最后一个答案对我有用,但我必须定义replace

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

希望能帮助到你。

我收到 TypeError:无法重新定义属性:使用 sinon 7.2.3 和 jsdom 13.2.0 进行分配。 不知道为什么这适用于某些人而不适用于其他人?

这对我有用:

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

我们使用pushState使其工作

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

这是一个困难的局面。 JSDOM 不完全支持导航(除了面包屑)并且 JSDOM 不允许我们模拟导航。 最终结果是我无法编写最终尝试触发 navigation 的测试

如果 JSDOM 确实学会了导航(我什至不确定这意味着什么),也许我可以断言在适当的页面上。 但是,对于我的测试用例,断言导航是触发的,而不是实际执行的,更清晰/更快。 这是我过去在使用 jsdom 进行测试时所做的,现在它已经坏了。

请允许我发布我自己问题的答案
我只是将window.location = url;window.location.href = url;的用法替换为

window.location.assign(url);

然后在我的测试中我做了:

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

像魅力一样工作 - 希望它可以对其他人有所帮助

如果您可以控制目标网址,但如果您正在加载 google 或 Instagram 网站,该怎么办。 或任何网站? 你怎么能解决这个问题?

根据@chrisbateman 的回答,我设法

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

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

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

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

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

我使用这个配置解决了这个问题。 我想测试哈希 url 的重定向:

    beforeEach(() => {
      delete global.window;
      global.window = {
        location: { replace: jest.fn(url => ({ href: url })) },
      };
    });
    it('should redirect hash url', () => {
      window.location.hash = '#/contrat?id=8171675304';
      global.window.location.href =
        'http://localhost:3000/#/contrat?id=8171675304';
      redirectHashUrl();

      expect(window.location.replace).toHaveBeenCalled();
    });

@chrisbateman @hamzahamidi谢谢,该解决方案运作良好。

也许这不是一个好的做法,但我们有一些测试,这些测试依赖于位置/主机/主机名和其他位置属性。 所以我们想要的模拟位置和之后恢复对我有用。

const realLocation = window.location;

describe('bla bla', () => {
  afterEach(() => {
    window.location = realLocation;
  });

  it('test where I want to use hostname', () => {
    delete window.location;
    window.location = { 
      hostname: 'my-url-i-expect.com'
    };
    // check my function that uses hostname
  });
});

这对我有用:

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

它不适用于CI类的 circleci

@hontas的解决方案有帮助:

我确实在我的代码中使用了window.location.assign(Config.BASE_URL);

这是测试:

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

window.location.assign.mockClear();

谢谢伙计,你救了我一天! :)

就我而言,我测试的查询字符串,并且因为我的规范的主体查询字符串本身,我不想绕过它,但我很高兴存根出来。 就我而言,这很有效:

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

请注意,这里窗口的标题是什么并不重要,它是/test.html (不是真实的)也无关紧要,重要的是查询字符串被正确获取(这通过)

示例的问题在于没有使用方法和 getter。 我所做的基本上是用 URL 对象替换 Location 对象。 URL 具有位置的所有属性(搜索、主机、哈希等)。

const realLocation = window.location;

describe('My test', () => {

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

    test('My test func', () => {

        // @ts-ignore
        delete window.location;

        // @ts-ignore
        window.location = new URL('http://google.com');

        // ...
    });
});

我也收到这个错误

Error: Not implemented: navigation (except hash changes)
    at module.exports (...\node_modules\jsdom\lib\jsdom\browser\not-implemented.js:9:17)
    at navigateFetch (...\node_modules\jsdom\lib\jsdom\living\window\navigation.js:74:3)

@hontas的解决方案有帮助:

我确实在我的代码中使用了window.location.assign(Config.BASE_URL);

这是测试:

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

window.location.assign.mockClear();

这对我有用,谢谢! 但是,我必须使用 jest test() 中的done参数,否则我无法确定期望是否已评估,并且无论如何测试可能已成功结束:

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

window.location.assign.mockClear();
}

@hontas对我不起作用:( 无法重写分配/替换属性

有什么方法可以找出哪个测试触发了“未实现:导航”消息? 我有一套 43 个测试 - 错误只显示一次,并且一直在跳来跳去。 我不知道要修复哪个测试!!! 堆栈跟踪没有给我任何罪魁祸首的迹象:

console.error
  Error: Not implemented: navigation (except hash changes)
      at module.exports (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
      at navigateFetch (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
      at exports.navigate (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
      at Timeout._onTimeout (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
      at listOnTimeout (internal/timers.js:531:17)
      at processTimers (internal/timers.js:475:7) undefined

    at VirtualConsole.<anonymous> (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
    at module.exports (node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12:26)
    at navigateFetch (node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
    at exports.navigate (node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
    at Timeout._onTimeout (node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)

@hontas对我不起作用:( 无法重写分配/替换属性

据我所知,jest 在新版本中改变了一些东西(“jest”:“^26.0.1”)所以现在可以正常工作:

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

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

有什么方法可以找出哪个测试触发了“未实现:导航”消息? 我有一套 43 个测试 - 错误只显示一次,并且一直在跳来跳去。 我不知道要修复哪个测试!!! 堆栈跟踪没有给我任何罪魁祸首的迹象:

console.error
  Error: Not implemented: navigation (except hash changes)
      at module.exports (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
      at navigateFetch (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
      at exports.navigate (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
      at Timeout._onTimeout (/Users/naresh/projects/mobx-state-router/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
      at listOnTimeout (internal/timers.js:531:17)
      at processTimers (internal/timers.js:475:7) undefined

    at VirtualConsole.<anonymous> (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
    at module.exports (node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12:26)
    at navigateFetch (node_modules/jsdom/lib/jsdom/living/window/navigation.js:76:3)
    at exports.navigate (node_modules/jsdom/lib/jsdom/living/window/navigation.js:54:3)
    at Timeout._onTimeout (node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)

很难判断哪个测试未实现错误来自。

我在node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12处添加一个断点,运行调试会话并等待达到断点。 只有这样我才能看到我应该改进哪个测试来摆脱这个消息。
image

有时需要 2-3 分钟,直到跑步者遇到问题才能进行测试。

PS:在我当前的项目中有 176 个 jsdom 相关测试

我目前正在使用 Jest 26.0.1并且以下对我有用:

  1. 使用window.location.assign(url)更改窗口位置。

  2. 模拟位置对象如下(请注意,不首先删除和重建对象仍然会导致在 Jest 的注释版本和更早版本中未实现的错误,我认为Object.defineProperty也不起作用,仍然导致错误):

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

如果 jest 允许我们轻松地模拟位置而没有所有这些不断出现的问题和变化,那就太好了。 我以前只使用window.location.assign = jest.fn()很长一段时间没有任何问题,然后我从 v24 升级,现在这个。

Window API 被锁定/冻结是否有充分的理由?
这似乎是人们试图解决未实施导航的障碍。

如果没有,我们可以不冷冻它们吗? 我认为即使是浏览器也不会严格阻止您更改窗口对象。

如果您有一个 jsdom 行为与浏览器不同的示例,请按照问题模板提交问题(包括 jsbin 或类似的显示浏览器行为)。

在 Jest 测试期间,是否有其他人遇到相同的Error: Not implemented: navigation (except hash changes)使用href触发锚元素上的点击事件? 对于我的测试,我正在监视一个名为onClick的函数并对此进行断言,因此我需要在锚元素上实际触发 click 事件。

上面与模拟window.location相关的解决方案适用于我,我明确调用window.location.replacewindow.location.assign ,但对于导航源自锚元素的这种情况没有帮助被点击。

关于解决方案的任何想法? 谢谢!

您将如何在浏览器中测试该代码? 请记住,在浏览器中单击该链接会吹走您的整个页面并丢弃任何测试结果。 因此,无论您采取什么措施来防止这种情况,也将防止 jsdom 输出到控制台的不太明显的警告消息。

在我的情况下,项目将 Jest 从 23 升级到 26 版本。
我对location.search 。 有同样的错误Error: Not implemented: navigation
我测试的模块获取搜索查询参数的值。
下一个实现对我有用:

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

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

@mattcphillips你知道如何解决这个问题吗? 我点击的时候也有问题

@Sabrinovsky不,我最终只是在我的 jest 配置中向setupFiles添加了一个脚本,它会吞下来自 jsdom 导航的控制台错误,这样它们就不会干扰我们的测试。 比其他任何东西都更像是创可贴,但听起来这些错误在我们的测试设置中是可以预料的。

这是我在测试之前运行的脚本:

// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that orginate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', error => {
  if (
    error.type !== 'not implemented' &&
    error.message !== 'Not implemented: navigation (except hash changes)' &&
    originalListener
  ) {
    originalListener(error);
  }

  // swallow error
});

这对我有用。

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

这对我有用

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

如果你得到TypeError: Cannot assign to read only property 'assign' of object '[object Location]' ,那么在你的jest.setup.ts使用类似的东西:

``
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
价值: {
...窗口.位置,
},
可写:真实,
});
````

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

potapovDim picture potapovDim  ·  4评论

khalyomede picture khalyomede  ·  3评论

josephrexme picture josephrexme  ·  4评论

tolmasky picture tolmasky  ·  4评论

kentmw picture kentmw  ·  3评论