| import fs from 'fs/promises' |
|
|
| import { afterEach, describe, expect, test } from 'vitest' |
| import yaml from 'js-yaml' |
| import MockDate from 'mockdate' |
|
|
| import { |
| createChangelogEntry, |
| cleanPreviewTitle, |
| previewAnchor, |
| prependDatedEntry, |
| getLastIgnoredChanges, |
| getIgnoredChangesSummary, |
| type ChangelogEntry, |
| } from '../scripts/build-changelog' |
| import readJsonFile from '@/frame/lib/read-json-file' |
|
|
| interface Preview { |
| title: string |
| description: string |
| toggled_by: string |
| announcement: any |
| updates: any |
| toggled_on: string[] |
| owning_teams: string[] |
| } |
|
|
| interface UpcomingChange { |
| location: string |
| description: string |
| date: string |
| } |
|
|
| interface IgnoredChange { |
| type: string |
| [key: string]: any |
| } |
|
|
| interface IgnoredChangesSummary { |
| totalCount: number |
| typeCount: number |
| types: Array<{ |
| type: string |
| count: number |
| }> |
| } |
|
|
| const expectedChangelogEntry: ChangelogEntry = readJsonFile( |
| 'src/graphql/tests/fixtures/changelog-entry.json', |
| ) as ChangelogEntry |
| const expectedUpdatedChangelogFile: ChangelogEntry[] = readJsonFile( |
| 'src/graphql/tests/fixtures/updated-changelog-file.json', |
| ) as ChangelogEntry[] |
|
|
| describe('creating a changelog from old schema and new schema', () => { |
| afterEach(() => { |
| MockDate.reset() |
| }) |
|
|
| test('ignores unknown change types without throwing errors', async () => { |
| |
| |
| const oldSchemaString = ` |
| type Query { |
| field: String |
| } |
| ` |
|
|
| const newSchemaString = ` |
| """ |
| Updated description for Query type |
| """ |
| type Query { |
| field: String |
| } |
| ` |
|
|
| |
| |
| const entry: ChangelogEntry | null = await createChangelogEntry( |
| oldSchemaString, |
| newSchemaString, |
| [], |
| [], |
| [], |
| ) |
|
|
| |
| |
| expect(entry).toBeNull() |
| }) |
|
|
| test('handles new directive usage change types gracefully', async () => { |
| |
| |
| const oldSchemaString = ` |
| directive @example on FIELD_DEFINITION |
| |
| type Query { |
| field: String |
| } |
| ` |
|
|
| const newSchemaString = ` |
| directive @example on FIELD_DEFINITION |
| |
| type Query { |
| field: String @example |
| } |
| ` |
|
|
| |
| |
| const entry: ChangelogEntry | null = await createChangelogEntry( |
| oldSchemaString, |
| newSchemaString, |
| [], |
| [], |
| [], |
| ) |
|
|
| |
| expect(entry).toBeNull() |
| }) |
|
|
| test('finds a diff of schema changes, upcoming changes, and preview changes', async () => { |
| const oldSchemaString = ` |
| type PreviewType { |
| field1(changeTypeArgument: Int): Int |
| } |
| |
| type Query { |
| stableField: String |
| removedField: Boolean |
| argumentsField( |
| removedRequiredArgument: Int! |
| removedOptionalArgument: Int! |
| argumentMadeRequired: Int |
| argumentMadeOptional: Int! |
| ): String |
| previewField: PreviewType |
| } |
| ` |
|
|
| const newSchemaString = ` |
| type PreviewType { |
| field1(changeTypeArgument: Float): Int |
| } |
| |
| type Query { |
| stableField: String |
| argumentsField( |
| argumentMadeRequired: Int! |
| argumentMadeOptional: Int |
| ): String |
| previewField: PreviewType! |
| } |
| ` |
|
|
| const previews = yaml.load(` |
| - title: Test preview |
| description: This preview is just for test |
| toggled_by: ':test_preview' |
| announcement: null |
| updates: null |
| toggled_on: |
| - PreviewType |
| - Query.previewField |
| owning_teams: |
| - '@github/engineering' |
| `) as Preview[] |
|
|
| const oldUpcomingChanges = yaml.load(` |
| upcoming_changes: |
| - location: EnterprisePendingCollaboratorEdge.isUnlicensed |
| description: '\`isUnlicensed\` will be removed.' |
| date: '2021-01-01T00:00:00+00:00' |
| `) as { upcoming_changes: UpcomingChange[] } |
| const oldUpcomingChangesArray: UpcomingChange[] = oldUpcomingChanges.upcoming_changes |
|
|
| const newUpcomingChanges = yaml.load(` |
| upcoming_changes: |
| - location: Query.stableField |
| description: '\`stableField\` will be removed.' |
| date: '2021-06-01T00:00:00+00:00' |
| - location: EnterprisePendingCollaboratorEdge.isUnlicensed |
| description: '\`isUnlicensed\` will be removed.' |
| date: '2021-01-01T00:00:00+00:00' |
| `) as { upcoming_changes: UpcomingChange[] } |
| const newUpcomingChangesArray: UpcomingChange[] = newUpcomingChanges.upcoming_changes |
|
|
| const entry: ChangelogEntry | null = await createChangelogEntry( |
| oldSchemaString, |
| newSchemaString, |
| previews, |
| oldUpcomingChangesArray, |
| newUpcomingChangesArray, |
| ) |
| expect(entry).toEqual(expectedChangelogEntry) |
| }) |
|
|
| test('returns null when there isnt any difference', async () => { |
| const schemaString = ` |
| type Query { |
| i: Int! |
| }` |
|
|
| const nullEntry: ChangelogEntry | null = await createChangelogEntry( |
| schemaString, |
| schemaString, |
| [], |
| [], |
| [], |
| ) |
| expect(nullEntry).toBeNull() |
| }) |
| }) |
|
|
| describe('Preparing preview links', () => { |
| test('fixes preview names', () => { |
| |
| expect(cleanPreviewTitle('UpdateRefsPreview')).toEqual('Update refs preview') |
| expect(cleanPreviewTitle('MergeInfoPreview')).toEqual('Merge info preview') |
| |
| expect(cleanPreviewTitle('something interesting')).toEqual('something interesting preview') |
| |
| expect(cleanPreviewTitle('nice preview')).toEqual('nice preview') |
| }) |
|
|
| test('creates anchors from preview titles', () => { |
| expect(previewAnchor('Merge info preview')).toEqual('merge-info-preview') |
| expect(previewAnchor('some.punct123 preview')).toEqual('somepunct123-preview') |
| }) |
| }) |
|
|
| describe('updating the changelog file', () => { |
| afterEach(() => { |
| MockDate.reset() |
| }) |
|
|
| test('modifies the entry object and the file on disk', async () => { |
| const testTargetPath = 'src/graphql/tests/fixtures/example-changelog.json' |
| const previousContents = await fs.readFile(testTargetPath) |
|
|
| const exampleEntry: ChangelogEntry = { |
| schemaChanges: [], |
| previewChanges: [], |
| upcomingChanges: [], |
| } |
| const expectedDate = '2020-11-20' |
| MockDate.set(expectedDate) |
|
|
| prependDatedEntry(exampleEntry, testTargetPath) |
| const newContents: string = await fs.readFile(testTargetPath, 'utf8') |
| |
| await fs.writeFile(testTargetPath, previousContents.toString()) |
|
|
| expect(exampleEntry).toEqual({ |
| schemaChanges: [], |
| previewChanges: [], |
| upcomingChanges: [], |
| date: expectedDate, |
| }) |
| expect(JSON.parse(newContents)).toEqual(expectedUpdatedChangelogFile) |
| }) |
| }) |
|
|
| describe('ignored changes tracking', () => { |
| test('tracks ignored change types', async () => { |
| const oldSchemaString = ` |
| type Query { |
| field: String |
| } |
| ` |
|
|
| const newSchemaString = ` |
| """ |
| Updated description for Query type |
| """ |
| type Query { |
| field: String |
| } |
| ` |
|
|
| |
| await createChangelogEntry(oldSchemaString, newSchemaString, [], [], []) |
|
|
| const ignoredChanges: IgnoredChange[] = getLastIgnoredChanges() |
| expect(ignoredChanges.length).toBe(1) |
| expect(ignoredChanges[0].type).toBe('TYPE_DESCRIPTION_ADDED') |
| }) |
|
|
| test('provides ignored changes summary', async () => { |
| const oldSchemaString = ` |
| directive @example on FIELD_DEFINITION |
| type Query { |
| field1: String |
| field2: Int |
| } |
| ` |
|
|
| const newSchemaString = ` |
| directive @example on FIELD_DEFINITION |
| type Query { |
| field1: String @example |
| field2: Int @example |
| } |
| ` |
|
|
| |
| await createChangelogEntry(oldSchemaString, newSchemaString, [], [], []) |
|
|
| const summary = getIgnoredChangesSummary() |
| expect(summary).toBeTruthy() |
| const typedSummary = summary as IgnoredChangesSummary |
| expect(typedSummary.totalCount).toBe(2) |
| expect(typedSummary.typeCount).toBe(1) |
| expect(typedSummary.types[0].type).toBe('DIRECTIVE_USAGE_FIELD_DEFINITION_ADDED') |
| expect(typedSummary.types[0].count).toBe(2) |
| }) |
|
|
| test('returns null summary when no changes ignored', async () => { |
| const schemaString = ` |
| type Query { |
| field: String |
| } |
| ` |
|
|
| |
| await createChangelogEntry(schemaString, schemaString, [], [], []) |
|
|
| const summary = getIgnoredChangesSummary() |
| expect(summary).toBeNull() |
| }) |
| }) |
|
|