chai: mutating chai exports no longer works in v5

As raised in chaijs/chai-http#310, the move to ESM actually had another breaking change we didn’t catch

in the past, you could do something like this:

const chai = require('chai');

chai.use((ex) => {
  ex['oogabooga'] = 303;
});

chai.oogabooga; // 303

of course, in es modules, this will no longer work:

import * as chai from 'chai';

chai.oogabooga = 303; // TypeError, object is not extensible

chai.use((ex) => {
  ex['oogabooga'] = 303; // works but doesn't mutate `chai`
});

chai.oogabooga; // undefined

we can’t really ‘fix’ this. imports are immutable objects when using *, so we can’t just stick things onto the end.

it means we probably need to make a decision of what the ‘new way’ of doing this is. some suggestions:

  • require that consumers do something like chai = chaiOriginal.use(foo).use(bar);
  • introduce some kind of chai.extensions, a mutable object we store these things in

cc @keithamus @koddsson

About this issue

  • Original URL
  • State: open
  • Created 6 months ago
  • Reactions: 4
  • Comments: 22 (10 by maintainers)

Commits related to this issue

Most upvoted comments

it is pretty much convenience, because people used to import chai as a default import (const chai = require(...)).

this just doesn’t work in esm anymore without exporting a chai const, which we shouldn’t really do imo.

so my suggestion is to just recommend the chai = chai.use(...) pattern.

otherwise, i think we end up with something like chai.ext.myplugin

I confirm, this drives me crazy. I downgrade back to v4.

@keithamus I have indeed been able to use your guidance to fix my issue with it! Thank you for that! It took less effort than I thought it would in the end.

FWIW this only impacts objects attached to the chai context from within a plugin. Regular assertions are attached to the Assertion prototype which is mutable in the module record. So only plugins which come with an additional “global” objects are effected. Those plugins can expose those objects as part of their module records, and we can also establish a convention that people should either import those objects from those libraries, or destructure the return value of the use() call. For example chai-http (an impacted plugin):

import * as chai from 'chai'
import {request}, chaiHttp from 'chai-http'
chai.use(chaiHttp)
request(...)

OR

import * as chai from 'chai'
import chaiHttp from 'chai-http'
const {request} = chai.use(chaiHttp)
request(...)

We can, as @43081j states, also add an object that is exported to simplify for other modules, so they don’t need to repeat the use calls or additional imports between files:

import * as chai from 'chai';

chai.ext.request(...)

// OR

const {request} = chai.ext
request(...)

@43081j done, see #1603

Thank you!

The docs under https://www.chaijs.com/plugins/chai-files/ are unfortunately not yet updated. Bit of a mess to introduce a breaking change and forget to update the docs IMHO. I love the work that the authors of this have put in but in this very case they messed up 😦

We’re all the author/maintainer of this software. You can make a PR to update the docs ❤️