text: import fetchMock from 'fetch-mock';
  
  import { resources, NEW, SUCCESS, RESOURCES } from '../../../index';
  import buildStore from '../../helpers/buildStore';
  
  describe('belongsTo:', function () {
    describe('when the association is one-to-one', function () {
      beforeAll(function () {
        this.initialState = {
          users: {
            items: {
              1: {
                values: {
                  id: 1,
                  username: 'Bob',
                  addressId: 1
                },
                status: { type: SUCCESS }
              },
  
              2: {
                values: {
                  id: 2,
                  username: 'Rupert',
                },
                status: { type: SUCCESS }
              },
  
              temp: {
                values: {
                  username: 'Jane',
                },
                status: { type: NEW }
              }
            },
            collections: {
              '': {
                positions: [ 1 ],
                status: { type: SUCCESS }
              }
            },
            selectionMap: { 1: true },
            newItemKey: 'temp'
          },
          addresses: {
            ...RESOURCES,
            items: {
              1: {
                values: {
                  city: 'City 1',
                  userId: 1
                },
                status: { type: SUCCESS }
              }
            }
          }
        };
      });
  
      afterAll(function () {
        fetchMock.restore();
      });
  
      beforeAll(function () {
  
        /**
         * @type {{actions, reducers, createAddress, updateAddress, destroyAddress}} resources
         */
        this.addresses = resources({
          name: 'addresses',
          url: 'http://test.com/addresses/:id?',
          keyBy: 'id'
        }, { create: true, update: true, destroy: true });
  
        const {
          reducers,
        } = resources({
          name: 'users',
          url: 'http://test.com/users/:id?',
          keyBy: 'id',
          belongsTo: {
            address: this.addresses
          }
        }, {
          index: true,
          new: true,
        });
  
        this.reducers = reducers;
      });
  
      describe('and the association\'s CREATE action occurs', function () {
        describe('before the request has completed', function () {
          beforeAll(function () {
            this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
            fetchMock.post('http://test.com/addresses', new Promise(resolve => {}));
  
            this.store.dispatch(this.addresses.actionCreators.createAddress('temp', { userId: 1, city: 'New City 3' }));
          });
  
          afterAll(function() {
            fetchMock.restore();
            this.store = null;
          });
  
          it('then sets the new association to the default attribute', function() {
            expect(this.store.getState().users.items[1].values.addressId).toEqual('temp');
          });
        });
  
        describe('and the request has completed', () => {
          beforeAll(function () {
            this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
            fetchMock.post('http://test.com/addresses', {
              body: { id: 3, userId: 1, city: 'New City 3' },
            });
  
            this.store.dispatch(this.addresses.actionCreators.createAddress('temp', { userId: 1, city: 'New City 3' }));
          });
  
          afterAll(function() {
            fetchMock.restore();
            this.store = null;
          });
  
          it('then updates the key of the association', function() {
            expect(this.store.getState().users.items[1].values.addressId).toEqual(3);
          });
        });
      });
  
      describe('and the association\'s UPDATE action occurs', function () {
        describe('and the previous values have been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userId: 2
              }, { previous: {
                  city: 'City 1',
                  userId: 1
                }
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
              expect(this.store.getState().users.items[2].values.addressId).toEqual(undefined);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', {
                body: {
                  city: 'City 1',
                  userId: 2
                },
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userId: 2
              }, { previous: {
                  city: 'City 1',
                  userId: 1
                }
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT display a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).not.toHaveBeenCalled();
            });
  
            it('then removes the associated item from old associated items', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
  
            it('then adds the associated item to new associated items', function() {
              expect(this.store.getState().users.items[2].values.addressId).toEqual(1);
            });
          });
        });
  
        describe('and the previous values have NOT been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userId: 2
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', {
                body: {
                  city: 'City 1',
                  userId: 2
                }
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userId: 2
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then displays a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).toHaveBeenCalledWith(
                'Redux and the REST: UPDATE_ADDRESS did not specify any previous values. This makes updating \'users.addressId\' much less efficient. Provide the values of the item you are destroying as the third argument to updateAddress().'
              );
            });
  
            it('then removes the associated item from old associated items', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
  
            it('then adds the associated item to new associated items', function() {
              expect(this.store.getState().users.items[2].values.addressId).toEqual(1);
            });
          });
        });
  
      });
  
      describe('and the association\'s DESTROY action occurs', function () {
        describe('and the previous values have been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1, {
                city: 'City 1',
                userId: 1
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', {
                body: {},
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1, {
                city: 'City 1',
                userId: 1
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT display a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).not.toHaveBeenCalled();
            });
  
            it('then removes the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
          });
        });
  
        describe('and the previous values have NOT been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', {
                body: {},
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then displays a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).toHaveBeenCalledWith(
                'Redux and the REST: DESTROY_ADDRESS did not specify any previous values. This makes updating \'users.addressId\' much less efficient. Provide the values of the item you are destroying as the second argument to destroyAddress().'
              );
            });
  
            it('then removes the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
          });
        });
  
      });
  
    });
  
    describe('when the association is one-to-many', function () {
      beforeAll(function () {
        this.initialState = {
          users: {
            items: {
              1: {
                values: {
                  id: 1,
                  username: 'Bob',
                  addressId: 1
                },
                status: { type: SUCCESS }
              },
  
              2: {
                values: {
                  id: 2,
                  username: 'Rupert',
                },
                status: { type: SUCCESS }
              },
  
              temp: {
                values: {
                  username: 'Jane',
                },
                status: { type: NEW }
              }
            },
            collections: {
              '': {
                positions: [ 1 ],
                status: { type: SUCCESS }
              }
            },
            selectionMap: { 1: true },
            newItemKey: 'temp'
          },
          addresses: {
            ...RESOURCES,
            items: {
              1: {
                values: {
                  city: 'City 1',
                  userIds: [ 1 ]
                },
                status: { type: SUCCESS }
              }
            }
          }
        };
      });
  
      afterAll(function () {
        fetchMock.restore();
      });
  
      beforeAll(function () {
        this.addresses = resources({
          name: 'addresses',
          url: 'http://test.com/addresses/:id?',
          keyBy: 'id'
        }, { create: true, update: true, destroy: true });
  
        const {
          reducers,
        } = resources({
          name: 'users',
          url: 'http://test.com/users/:id?',
          keyBy: 'id',
          belongsTo: {
            addresses: {
              ...this.addresses,
            },
          }
        }, {
          index: true,
          new: true,
        });
  
        this.reducers = reducers;
      });
  
      describe('and the association\'s CREATE action occurs', function () {
        describe('before the request has completed', function () {
          beforeAll(function () {
            this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
            fetchMock.post('http://test.com/addresses', new Promise(resolve => {}));
  
            this.store.dispatch(this.addresses.actionCreators.createAddress('temp', { userIds: [ 1 ], city: 'New City 3' }));
          });
  
          afterAll(function() {
            fetchMock.restore();
            this.store = null;
          });
  
          it('then adds the new association to the default attribute', function() {
            expect(this.store.getState().users.items[1].values.addressId).toEqual('temp');
          });
        });
  
        describe('and the request has completed', () => {
          beforeAll(function () {
            this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
            fetchMock.post('http://test.com/addresses', {
              body: { id: 3, userIds: [1], city: 'New City 3' },
            });
  
            this.store.dispatch(this.addresses.actionCreators.createAddress('temp', { userIds: [ 1 ], city: 'New City 3' }));
          });
  
          afterAll(function() {
            fetchMock.restore();
            this.store = null;
          });
  
          it('then updates the key of the association', function() {
            expect(this.store.getState().users.items[1].values.addressId).toEqual(3);
          });
        });
      });
  
      describe('and the association\'s UPDATE action occurs', function () {
        describe('and the previous values have been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userIds: [ 2 ]
              }, { previous: {
                  city: 'City 1',
                  userIds: [ 1 ]
                }
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
              expect(this.store.getState().users.items[2].values.addressId).toEqual(undefined);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', {
                body: {
                  city: 'City 1',
                  userIds: [2]
                }
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userIds: [ 2 ]
              }, { previous: {
                  city: 'City 1',
                  userIds: [ 1 ]
                }
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT display a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).not.toHaveBeenCalled();
            });
  
            it('then removes the associated item from old associated items', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
  
            it('then adds the associated item to new associated items', function() {
              expect(this.store.getState().users.items[2].values.addressId).toEqual(1);
            });
          });
        });
  
        describe('and the previous values have NOT been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userIds: [ 2 ]
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.put('http://test.com/addresses/1', {
                    body: {
                      city: 'City 1',
                      userIds: [2]
                    },
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.updateAddress(1, {
                city: 'City 1',
                userIds: [ 2 ]
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then displays a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).toHaveBeenCalledWith(
                'Redux and the REST: UPDATE_ADDRESS did not specify any previous values. This makes updating \'users.addressId\' much less efficient. Provide the values of the item you are destroying as the third argument to updateAddress().'
              );
            });
  
            it('then removes the associated item from old associated items', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
  
            it('then adds the associated item to new associated items', function() {
              expect(this.store.getState().users.items[2].values.addressId).toEqual(1);
            });
          });
        });
  
      });
  
      describe('and the association\'s DESTROY action occurs', function () {
        describe('and the previous values have been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1, {
                city: 'City 1',
                userIds: [ 1 ]
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', {
                    body: {},
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1, {
                city: 'City 1',
                userIds: [ 1 ]
              }));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT display a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).not.toHaveBeenCalled();
            });
  
            it('then removes the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
          });
        });
  
        describe('and the previous values have NOT been specified', () => {
          describe('before the request has completed', function () {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', new Promise(resolve => {}));
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then does NOT remove the associated item', function () {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(1);
            });
          });
  
          describe('and the request has completed', () => {
            beforeAll(function () {
              this.store = buildStore({ ...this.initialState }, { users: this.reducers, addresses: this.addresses.reducers });
  
              fetchMock.delete('http://test.com/addresses/1', {
                    body: {},
              });
  
              spyOn(console, 'warn');
  
              this.store.dispatch(this.addresses.actionCreators.destroyAddress(1));
            });
  
            afterAll(function() {
              fetchMock.restore();
              this.store = null;
            });
  
            it('then displays a warning', function() {
              // eslint-disable-next-line no-console
              expect(console.warn).toHaveBeenCalledWith(
                'Redux and the REST: DESTROY_ADDRESS did not specify any previous values. This makes updating \'users.addressId\' much less efficient. Provide the values of the item you are destroying as the second argument to destroyAddress().'
              );
            });
  
            it('then removes the associated item', function() {
              expect(this.store.getState().users.items[1].values.addressId).toEqual(undefined);
            });
          });
        });
      });
    });
  });
  