4

Here's my code to test equality of some class objects. See my other question if you want to know why I'm not just doing expect(receivedDeals).toEqual(expectedDeals) and other simpler assertions.

   type DealCollection = { [key: number]: Deal };  // imported from another file

   it("does the whole saga thing", async () => {
      sagaStore.dispatch(startAction);
      await sagaStore.waitFor(successAction.type);
      const calledActionTypes: string[] = sagaStore
        .getCalledActions()
        .map(a => a.type);
      expect(calledActionTypes).toEqual([startAction.type, successAction.type]);
      const receivedDeals: DealCollection = sagaStore.getLatestCalledAction()
        .deals;
      Object.keys(receivedDeals).forEach((k: string) => {
        const id = Number(k);
        const deal = receivedDeals[id];
        const expected: Deal = expectedDeals[id];
        for (let key in expected) {
          if (typeof expected[key] === "function") continue;
          expect(expected[key]).toEqual(deal[key]);
        }
      });
    });

The test passes fine, but I'm getting a Flow error on expected[key]:

Cannot get 'expected[key]' because an index signature declaring the expected key / value type is missing in 'Deal'

I can paste in code from Deal by request, but I think all you need to know is that I haven't declared an index signature (because I don't know how!).

I've searched around a bit but I can't find this exact case.

Update: I can eliminate the errors by changing deal and expected thusly:

const deal: Object = { ...receivedDeals[id] };
const expected: Object = { ...expectedDeals[id] };

And since I'm comparing properties in the loop this isn't really a problem. But I would think that I should be able to do this with Deals, and I'd like to know how I declare the index signature mentioned in the error.

PS. Bonus question: In some world where a mad scientist crossbred JS with Swift, I imagine you could do something like

const deal: Object = { ...receivedDeals[id] where (typeof receivedDeals[id] !== "function" };
const expected = // same thing
expect(deal).toEqual(expected);

// And then after some recombining of objects:
expect(receivedDeals).toEqual(expectedDeals);

Is this a thing at all?

Edit:

Adding a bit of the definition of Deal class:

Deal.js (summary)

export default class Deal {
  obj: { [key: mixed]: mixed };
  id: number;
  name: string;
  slug: string;
  permalink: string;
  headline: string;
  // ...other property definitions

  constructor(obj?: Object) {
    if (!obj) return;
    this.id = obj.id;
    this.name = obj.name;
    this.headline = obj.headline;
    // ...etc
  }

  static fromApi(obj: Object): Deal {
    const deal = new Deal();
    deal.id = obj.id;
    deal.name = obj.name;
    deal.slug = obj.slug;
    deal.permalink = obj.permalink;
    // ...etc
    return deal;
  }

  descriptionWithTextSize(size: number): string {
    return this.descriptionWithStyle(`font-size:${size}`);
  }

  descriptionWithStyle(style: string): string {
    return `<div style="${style}">${this.description}</div>`;
  }

  distanceFromLocation = (
    location: Location,
    unit: unitOfDistance = "mi"
  ): number => {
    return distanceBetween(this.location, location);
  };

  distanceFrom = (otherDeal: Deal, unit: unitOfDistance = "mi"): number => {
    return distanceBetween(this.location, otherDeal.location);
  };

  static toApi(deal: Deal): Object {
    return { ...deal };
  }

  static collectionFromArray(array: Object[]) {
    const deals: DealCollection = {};
    array.forEach(p => (deals[p.id] = Deal.fromApi(p)));
    return deals;
  }
}
Community
  • 1
  • 1
Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129

1 Answers1

4

An index signature (or indexer property) is defined as [keyName: KeyType]: ValueType. DealCollection is a great example: the keyName is key, the KeyType is number and the ValueType is Deal. This means that whenever you access a number property of an object of type DealCollection, it will return a Deal. You will want to add a similar expression to the definition of Deal in order to access arbitrary properties on it. More information can be found at the Objects as maps section in the Flow documentation.

user11307804
  • 828
  • 4
  • 12
  • Thanks, and I think I understand what you're saying. But what is the syntax of the expression to add? I'd tried adding `{ [key: mixed]: mixed };` with the property definitions (see the code for `Deal` that I've added above; before adding any attempt at a signature) but that is a big syntax error. I changed it to ` obj: { [key: mixed]: mixed };` but that has no effect on the Flow errors. – Jonathan Tuzman Jul 03 '19 at 13:17
  • Oh, I see the issue now. Flow types the return of `Object.keys` as a `string[]` so Flow will not be able to infer that `deal[key]` is defined. I don't know if there's a good Flow solution for that. However, I would recommend using [`toMatchObject`](https://jestjs.io/docs/en/expect#tomatchobjectobject) and/or [`expect.any(Function)`](https://jestjs.io/docs/en/expect#expectanyconstructor) instead of doing a loop through all of the keys. – user11307804 Jul 09 '19 at 14:07