mswjs / data

Data modeling and relation library for testing JavaScript applications.
https://npm.im/@mswjs/data
MIT License
822 stars 52 forks source link

Factory model for a Union Type model #302

Open SalahAdDin opened 8 months ago

SalahAdDin commented 8 months ago

Right now we have the following definition for our models:

type TArticleBase = {
  /** article's thumbnail */
  thumbnail: TImage;
  /** article's title */
  title: string;
  /** article's category */
  category: TCategory;
  /** article's publication date */
  publishedDate: Date;
  /** article's creation date */
  createdDate: Date;
  /** article's slug for URL path */
  slug: string;
  /** article's editor */
  editor: Array<TUser>;
  /** article's hero */
  hero: TArticleHero;
  /** article's source */
  source?: string;
  /** article's tags */
  tags: Array<string>;
};

export type TArticleType = (typeof ARTICLE_TYPES)[number];

export type TStandardArticle = TArticleBase & {
  /** states this article is a standard article */
  type: "standard";
  /** article's introduction (used also as meta) */
  introduction: string;
  /** article's author */
  author?: Array<TUser>;
  /** article's audio */
  audio?: TAudio;
  /** article's content */
  content: TRichText;
  /** article's continue reading article */
  continue: TContinueReadingArticle;
  /** article's related articles */
  related?: Array<TRelatedArticle>;
};

export type TFeaturedArticle = Omit<TStandardArticle, "related" | "type"> & {
  /** states this article is a featured article */
  type: "featured";
};

export type TLiveArticle = TArticleBase & {
  /** states this article is a live article */
  type: "live";
  /** lists the main ideas of the article */
  ideas: Array<string>;
  /** list of updates on the article */
  content: Map<string, Array<TUpdatedInformation>>;
  /** article's related articles */
  related?: Array<TRelatedArticle>;
};

export type TArticle = TFeaturedArticle | TLiveArticle | TStandardArticle;

And the following factory:


export const articleFactory: ModelDefinition = {
  id: primaryKey(() => _articleId++),
  type: () => chooseOne(ARTICLE_TYPES),
  title: () => baseTitle,
  slug: () => faker.helpers.slugify(baseTitle),
  author: nullable(manyOf("user")),
  editor: nullable(manyOf("user")),
  thumbnail: oneOf("image"),
  category: oneOf("category"),
  createdDate: () => faker.date.anytime(),
  publishedDate: () => faker.date.anytime(),
  introduction: () => faker.lorem.text(),
  content: {
    // TODO: https://github.com/mswjs/data/issues/299
    root: () => mockRichText({}),
  },
  audio: {
    url: () => DEFAULT_AUDIO_URL,
    duration: () => 7264000,
    mimeType: () => chooseOne(AUDIO_MIME_TYPES),
  },
  hero: {
    url: () => faker.image.url({ width: mockWidth, height: mockHeight }),
    alt: () => faker.lorem.text(),
    width: () => mockWidth,
    height: () => mockHeight,
    mimeType: () => chooseOne(IMAGE_MIME_TYPES),
    caption: {
      source: () => faker.lorem.word(),
      description: () => faker.lorem.text(),
    },
  },
  source: nullable(() => faker.lorem.word()),
  tags: () => mockArray(6, () => faker.lorem.word()),
  continue: oneOf("continueArticle"),
  related: nullable(manyOf("relatedArticle")),
};

This factory can create different combinations based on the article type.

We don't want to create a different factory for every article type since it adds unnecessary complexity to querying the entities.

What is the best way to do it? Does it require a new feature?

Thank you

SalahAdDin commented 8 months ago

Right now we have the following issue:

const targetType = chooseOne(ARTICLE_TYPES);
const targetContent =
  targetType === "live"
    ? mockArray(5, () => mockUpdatedInformation({}))
    : {
        root: mockRichText({}),
      };

export const articleFactory: ModelDefinition = {
  id: primaryKey(() => _articleId++),
  type: () => targetType,
  title: () => baseTitle,
  slug: () => slugify(baseTitle),
  author: nullable(manyOf("user")),
  editor: nullable(manyOf("user")),
  thumbnail: oneOf("image"),
  category: oneOf("category"),
  createdDate: () => faker.date.anytime(),
  publishedDate: () => faker.date.anytime(),
  introduction: () => faker.lorem.text(),
  summary: () => mockArray(3, () => faker.lorem.text()),
  // TODO: https://github.com/mswjs/data/issues/299
  content: () => targetContent,
  audio: {
    url: () => DEFAULT_AUDIO_URL,
    duration: () => 7264000,
    mimeType: () => chooseOne(AUDIO_MIME_TYPES),
  },
  hero: {
    url: () => faker.image.url({ width: mockWidth, height: mockHeight }),
    alt: () => faker.lorem.text(),
    width: () => mockWidth,
    height: () => mockHeight,
    mimeType: () => chooseOne(IMAGE_MIME_TYPES),
    caption: {
      source: () => faker.lorem.word(),
      description: () => faker.lorem.text(),
    },
  },
  source: nullable(() => faker.lorem.word()),
  tags: () => mockArray(6, () => faker.lorem.word()),
  continue: nullable(oneOf("continueArticle")),
  related: manyOf("relatedArticle"),
};

And we are creating the articles like this:


  /** Create an article. Faker will fill in any missing data */
  const createArticle = (article = {}) => db.article.create(article);

  /** Creating an article based on the figma design */
  createArticle({
    ...mockArticle({
      title: STANDARD_ARTICLE_BASE.title,
      introduction: STANDARD_ARTICLE_BASE.introduction,
      hero: {
        ...mockImage({ alt: STANDARD_ARTICLE_BASE.hero.text }),
        caption: mockCaption({
          source: STANDARD_ARTICLE_BASE.hero.source,
          description: STANDARD_ARTICLE_BASE.hero.text,
        }),
      },
      source: STANDARD_ARTICLE_BASE.source,
      tags: STANDARD_ARTICLE_BASE.tags,
    }),
    author: users[2],
    editor: chooseOne(users),
    thumbnail: chooseOne(imageGallery),
    category: figmaCategories[0],
    continue: figmaContinueArticles[0],
    related: figmaRelatedArticles.slice(0, 5),
    content: {
      root: mockArticleContent({ raw: STANDARD_ARTICLE_CONTENT }),
    },
  });

  /** Creating a live article based on the figma design */
  createArticle({
    ...mockLiveArticle({
      title: LIVE_ARTICLE_BASE.title,
      summary: LIVE_ARTICLE_BASE.summary,
      tags: LIVE_ARTICLE_BASE.tags,
    }),
    content: mockBELiveArticleContent(),
    editor: chooseOne(users),
    thumbnail: chooseOne(imageGallery),
    category: figmaCategories[1],
    related: figmaRelatedArticles.slice(4, 10),
  });

  /** Creating a featured article based on the figma design */
  createArticle({
    ...mockFeaturedArticle({
      title: FEATURED_ARTICLE_BASE.title,
      introduction: FEATURED_ARTICLE_BASE.introduction,
    }),
    type: "featured",
    display: chooseOne(DISPLAY_TYPES),
    category: figmaCategories[2],
    author: users[0],
    editor: chooseOne(users),
    thumbnail: chooseOne(imageGallery),
    continue: figmaContinueArticles[1],
    related: figmaRelatedArticles.slice(-2),
    content: {
      root: mockArticleContent({ raw: FEATURED_ARTICLE_CONTENT }),
    },
  });

So for a Live Article, the content should be an array of items, and it is: image

But for a non Live Article, the content should be a rich-text object, and it does not work: image

Before we had the content created with the mock function and not the content we passed to the function, that's not what we wanted.

As a solution for getting the right content, we have to set the content directly:

const targetContent = /* targetType === "live"
    ? mockArray(5, () => mockUpdatedInformation({}))
    : */ {
  root: mockRichText({}),
};

*
*
*
content: targetContent,

Yeah, content: () => targetContent, does not work also.