Back to Repositories

Validating YAML Author Configuration in GatsbyJS

This test suite validates the YAML author configuration in the Gatsby blog system, ensuring proper author entries with required fields and avatar image specifications. It verifies data integrity and file existence for blog author metadata.

Test Coverage Overview

The test suite provides comprehensive coverage for author YAML validation in the Gatsby blog system.

Key areas tested include:
  • Required field validation (id, bio, twitter, avatar)
  • Field type verification for string values
  • Avatar image extension validation
  • File existence verification for avatar images
Edge cases cover invalid field types, missing required fields, and non-existent avatar files.

Implementation Analysis

The implementation uses Jest’s mocking capabilities to simulate the GitHub API and file system interactions. The testing approach focuses on isolated unit tests with mocked dependencies.

Key patterns include:
  • Mock reset before each test
  • Utility function mocking for error handling
  • Dynamic YAML content injection
  • Asynchronous validation testing

Technical Details

Testing tools and configuration:
  • Jest test framework
  • TypeScript implementation
  • Danger JS integration
  • Mock utilities for GitHub API
  • YAML validation helpers
  • BeforeEach test setup
  • Async/await pattern for validation calls

Best Practices Demonstrated

The test suite exemplifies strong testing practices through modular test organization and thorough validation coverage.

Notable practices include:
  • Isolated test cases with clear purposes
  • Comprehensive mock cleanup
  • Explicit error message validation
  • Structured test grouping
  • Consistent assertion patterns

gatsbyjs/gatsby

peril/tests/validate-yaml/validate-yaml-authors.test.ts

            
jest.mock("danger", () => jest.fn())
import * as danger from "danger"
import { validateYaml, utils } from "../../rules/validate-yaml"
const dm = danger as any
const mockedUtils = utils as any

let mockedResponses: { [id: string]: any }
const setAuthorYmlContent = (content: string) =>
  (mockedResponses["docs/blog/author.yaml"] = content)
const setAvatarFiles = (filesnames: string[]) =>
  (mockedResponses["docs/blog/avatars"] = {
    data: filesnames.map(filename => ({ name: filename })),
  })
const resetMockedResponses = () => {
  mockedResponses = {}
  setAvatarFiles([])
}

dm.warn = jest.fn()
mockedUtils.addErrorMsg = jest.fn()

beforeEach(() => {
  resetMockedResponses()
  dm.warn.mockClear()
  mockedUtils.addErrorMsg.mockClear()
  dm.danger = {
    git: {
      modified_files: ["docs/blog/author.yaml"],
    },
    github: {
      api: {
        repos: {
          getContent: ({ path }: { path: string }) => mockedResponses[path],
        },
      },
      pr: {
        head: {
          repo: {
            full_name: "test/test",
          },
          ref: "branch",
        },
      },
      utils: {
        fileContents: (path: string) => mockedResponses[path],
      },
    },
  }
})

describe("a new PR", () => {
  it(`Valid entry passes validation`, async () => {
    setAuthorYmlContent(`
      - id: lorem
        bio: ipsum
        twitter: '@lorem'
        avatar: avatars/lorem.jpg
    `)
    setAvatarFiles(["lorem.jpg"])

    await validateYaml()
    expect(dm.warn).not.toBeCalled()
    expect(mockedUtils.addErrorMsg).not.toBeCalled()
  })

  it(`Check for required fields`, async () => {
    setAuthorYmlContent(`
      - twitter: '@lorem'
    `)

    await validateYaml()
    expect(dm.warn).toBeCalled()
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledTimes(3)
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"id" is required'),
      expect.anything()
    )
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"bio" is required'),
      expect.anything()
    )
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"avatar" is required'),
      expect.anything()
    )
  })

  it(`Check type of fields`, async () => {
    setAuthorYmlContent(`
    - id: 1
      bio: 2
      twitter: 3
      avatar: 4
    `)

    await validateYaml()
    expect(dm.warn).toBeCalled()
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledTimes(4)
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"id" must be a string'),
      expect.anything()
    )
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"bio" must be a string'),
      expect.anything()
    )
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"twitter" must be a string'),
      expect.anything()
    )
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"avatar" must be a string'),
      expect.anything()
    )
  })

  it(`Doesn't allow not supported extensions`, async () => {
    setAuthorYmlContent(`
      - id: lorem
        bio: ipsum
        twitter: '@lorem'
        avatar: avatars/lorem.svg
    `)
    setAvatarFiles(["lorem.svg"])

    await validateYaml()
    expect(dm.warn).toBeCalled()
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledTimes(1)
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"avatar" need to use supported extension'),
      expect.anything()
    )
  })

  it(`Doesn't allow not existing images`, async () => {
    setAuthorYmlContent(`
      - id: lorem
        bio: ipsum
        twitter: '@lorem'
        avatar: avatars/lorem.jpg
    `)
    setAvatarFiles(["ipsum.jpg"])

    await validateYaml()
    expect(dm.warn).toBeCalled()
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledTimes(1)
    expect(mockedUtils.addErrorMsg).toHaveBeenCalledWith(
      0,
      expect.stringContaining('"avatar" need to point to existing file'),
      expect.anything()
    )
  })
})