Skip to content

Commit

Permalink
Merge pull request #516 from ottofeller/expand-test-coverage
Browse files Browse the repository at this point in the history
Expand test coverage
  • Loading branch information
gvidon authored Jan 23, 2024
2 parents 43ec28e + 935b029 commit a88aeac
Show file tree
Hide file tree
Showing 12 changed files with 1,542 additions and 13 deletions.
36 changes: 30 additions & 6 deletions src/common/files/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,39 @@ describe('AssetFile util', () => {
expect(asset).toEqual('test asset updated\n')
})

test('formats JS/TS code with prettier after template replacement', () => {
test('can utilize multiple templates for replacement', () => {
const arrayTemplate = [
{templateString: '{{REPLACE', replacement: 'updated'},
{templateString: 'ME}}', replacement: 'twice'},
]

const project = new TestProject()
new AssetFile(project, 'asset.txt', {sourcePath, template: arrayTemplate})
const snapshot = synthSnapshot(project)
const asset = snapshot['asset.txt']
expect(asset).toBeDefined()
expect(asset).toEqual('test asset updated twice\n')
})

describe('prettier formatting after template replacement', () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TS is not aware of the Jest mock, thus casting is needed
const mockedFormat = format as unknown as jest.Mock<string, [string, Options]>
mockedFormat.mockReturnValue('formatted content')
const project = new TestProject()
new AssetFile(project, 'asset.ts', {sourcePath, template})
synthSnapshot(project)
expect(mockedFormat).toHaveBeenCalledTimes(1)
expect(mockedFormat).toHaveBeenCalledWith('test asset updated\n', expect.any(Object))

test('applied to JS/TS code', () => {
const project = new TestProject()
new AssetFile(project, 'asset.ts', {sourcePath, template})
synthSnapshot(project)
expect(mockedFormat).toHaveBeenCalledTimes(1)
expect(mockedFormat).toHaveBeenCalledWith('test asset updated\n', expect.any(Object))
})

test('does not apply to non JS/TS files', () => {
const project = new TestProject()
new AssetFile(project, 'asset.rs', {sourcePath, template})
synthSnapshot(project)
expect(mockedFormat).not.toHaveBeenCalled()
})
})
})

Expand Down
49 changes: 49 additions & 0 deletions src/common/git/husky/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,55 @@ describe('addHusky function', () => {
})
})

describe('pre-commit hook with checkCargo rule', () => {
const cargoCheckCommand = 'cargo check'

test('is skiped by default', () => {
const project = new TestProject()
addHusky(project, {})
const snapshot = synthSnapshot(project)
expect(snapshot['package.json'].devDependencies).toHaveProperty('husky')
expect(snapshot[preCommitShellScriptDestinationPath]).not.toBeDefined()
})

test('runs check and formatting commands with default settings', () => {
const project = new TestProject()
addHusky(project, {huskyRules: {checkCargo: {ignoreBranches: []}}})
const snapshot = synthSnapshot(project)
const fullCommand = ['cargo fmt --all', cargoCheckCommand].join('\n')
const shellScriptContents = templateContents.replace('{{COMMAND}}', fullCommand)
expect(snapshot[preCommitShellScriptDestinationPath]).toEqual(shellScriptContents)
})

test('checks all branches with empty branch list', () => {
const project = new TestProject()
addHusky(project, {huskyRules: {checkCargo: {ignoreBranches: [], isFormatting: false}}})
const snapshot = synthSnapshot(project)
const shellScriptContents = templateContents.replace('{{COMMAND}}', cargoCheckCommand)
expect(snapshot[preCommitShellScriptDestinationPath]).toEqual(shellScriptContents)
})

test('allows to ignore particular branches', () => {
const project = new TestProject()
const ignoredBranch = 'special-branch'
addHusky(project, {huskyRules: {checkCargo: {ignoreBranches: [ignoredBranch], isFormatting: false}}})
const snapshot = synthSnapshot(project)
const fullCommand = `if check_branch "${ignoredBranch}"; then\n ${cargoCheckCommand}\nfi\n`
const shellScriptContents = templateContents.replace('{{COMMAND}}', fullCommand)
expect(snapshot[preCommitShellScriptDestinationPath]).toEqual(shellScriptContents)
})

test('performs the check in the specified workingDirectory', () => {
const project = new TestProject()
const workingDirectory = 'testDir'
addHusky(project, {huskyRules: {checkCargo: {ignoreBranches: [], isFormatting: false, workingDirectory}}})
const snapshot = synthSnapshot(project)
const fullCommand = [`cd ${workingDirectory}`, cargoCheckCommand, 'cd ..'].join('\n')
const shellScriptContents = templateContents.replace('{{COMMAND}}', fullCommand)
expect(snapshot[preCommitShellScriptDestinationPath]).toEqual(shellScriptContents)
})
})

test('adds hooks from a user-defined list', () => {
const project = new TestProject()
const checkCargo: CheckCargoOptions = {isFormatting: false}
Expand Down
54 changes: 53 additions & 1 deletion src/common/github/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ describe('GitHub utils', () => {
})
})

test('includes lighthouse job when requested', () => {
const project = new TestProject()
new PullRequestTest(project.github!, {isLighthouseEnabled: true})
const snapshot = synthSnapshot(project)
const workflow = YAML.parse(snapshot[testWorkflowPath])
expect(workflow.jobs.lighthouse.steps.at(-2).run).toEqual('npm run lighthouse')
})

describe('addToProject', () => {
const name = 'subproject'
const subprojectTestWorkflowPath = `.github/workflows/test-${name}.yml`
Expand Down Expand Up @@ -214,6 +222,20 @@ describe('GitHub utils', () => {
})
})
})

test('does not add test job only if jest is disabled', () => {
const project = new TestProjectWithTestWorkflow({jest: false})
const snapshot = synthSnapshot(project)
const workflow = YAML.parse(snapshot[testWorkflowPath])
expect(workflow.jobs.test).not.toBeDefined()
})

test('propagates isLighthouseEnabled option to constructor', () => {
const project = new TestProjectWithTestWorkflow({isLighthouseEnabled: true})
const snapshot = synthSnapshot(project)
const workflow = YAML.parse(snapshot[testWorkflowPath])
expect(workflow.jobs.lighthouse.steps.at(-2).run).toEqual('npm run lighthouse')
})
})
})

Expand Down Expand Up @@ -284,6 +306,36 @@ describe('GitHub utils', () => {
expect(jobs[0]).toEqual('Check git')
})

describe('allows setting nodeVersion', () => {
const nodeVersion = '18.15.0'

test('from workflow options', () => {
const project = new TestProject()
new ProjenDriftCheckWorkflow(project.github!, {workflowNodeVersion: nodeVersion})
const snapshot = synthSnapshot(project)
const workflow = YAML.parse(snapshot[workflowPath])

Object.values<Job>(workflow.jobs)
.map((job) => job.steps.find((step) => step.uses === `actions/setup-node@v3`))
.forEach((job) => {
expect(job!.with!['node-version']).toEqual(nodeVersion)
})
})

test('from project package', () => {
const project = new TestProject({minNodeVersion: nodeVersion})
new ProjenDriftCheckWorkflow(project.github!)
const snapshot = synthSnapshot(project)
const workflow = YAML.parse(snapshot[workflowPath])

Object.values<Job>(workflow.jobs)
.map((job) => job.steps.find((step) => step.uses === `actions/setup-node@v3`))
.forEach((job) => {
expect(job!.with!['node-version']).toEqual(nodeVersion)
})
})
})

describe('addToProject', () => {
const name = 'subproject'

Expand Down Expand Up @@ -543,7 +595,7 @@ class TestProject extends NodeProject {
}

class TestProjectWithTestWorkflow extends NodeProject {
constructor(options: Partial<NodeProjectOptions & WithDefaultWorkflow> = {}) {
constructor(options: Partial<NodeProjectOptions & WithDefaultWorkflow & {isLighthouseEnabled: boolean}> = {}) {
super({
name: 'test-project-with-test-workflow',
defaultReleaseBranch: 'main',
Expand Down
2 changes: 1 addition & 1 deletion src/common/github/pull-request-test-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface PullRequestTestOptions
/**
* Setup Lighthouse audit script & GitHub job.
*
* @default true
* @default false
*/
readonly isLighthouseEnabled?: boolean
}
Expand Down
19 changes: 18 additions & 1 deletion src/common/tasks/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('addTaskOrScript function', () => {
const taskName = 'do-some-work'
const step1 = 'dig'
const step2 = 'rest'
addTaskOrScript(project, taskName, {steps: [{exec: step1}, {exec: step2}]})
addTaskOrScript(project, taskName, {steps: [{exec: step1}, {spawn: step2}]})

const snapshot = synthSnapshot(project)
const {scripts} = snapshot['package.json']
Expand All @@ -67,6 +67,23 @@ describe('addTaskOrScript function', () => {
delete process.env.PROJEN_EJECTING
}
})

test('adds an empty script to package.json of an ejected project as a final fallback', () => {
const PROJEN_EJECTING = process.env
process.env.PROJEN_EJECTING = '1'

const project = new TestProject()
const taskName = 'do-some-work'
addTaskOrScript(project, taskName, {})

const snapshot = synthSnapshot(project)
const {scripts} = snapshot['package.json']
expect(scripts[taskName]).toEqual('')

if (PROJEN_EJECTING !== undefined) {
delete process.env.PROJEN_EJECTING
}
})
})

class TestProject extends NodeProject {
Expand Down
61 changes: 59 additions & 2 deletions src/common/telemetry/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {JavaProject} from 'projen/lib/java'
import {NodeProject, NodeProjectOptions} from 'projen/lib/javascript'
import {synthSnapshot} from 'projen/lib/util/synth'
import * as YAML from 'yaml'
import {IWithTelemetryReportUrl, WithTelemetry, setupTelemetry} from '..'
import {reportTargetAuthToken} from '../collect-telemetry'
import {TelemetryWorkflow} from '../telemetry-workflow'

describe('setupTelemetry function', () => {
const telemetryWorkflowPath = '.github/workflows/telemetry.yml'
const telemetryWorkflowPath = '.github/workflows/telemetry.yml'

describe('setupTelemetry function', () => {
test('does nothing if telemetry options are not provided', () => {
const project = new TestProject()
setupTelemetry(project, {})
Expand All @@ -17,6 +19,41 @@ describe('setupTelemetry function', () => {
expect(telemetryWorkflow).toBeUndefined()
})

describe('throws in case if unexpected usage', () => {
test('if telemetryOptions are set without enabling telemetry', () => {
const project = new TestProject()
const telemetryOptions = {reportTargetUrl: 'http://localhost:3000/telemetry'}
expect(() => setupTelemetry(project, {telemetryOptions})).toThrow()
})

test('if telemetry enabling without any options', () => {
const project = new TestProject()
expect(() => setupTelemetry(project, {isTelemetryEnabled: true})).toThrow()
})

test('if reportTargetAuthHeaderName set without reportTargetAuthTokenVar', () => {
const project = new TestProject()

const telemetryOptions = {
reportTargetUrl: 'http://localhost:3000/telemetry',
reportTargetAuthHeaderName: 'auth',
}

expect(() => setupTelemetry(project, {telemetryOptions})).toThrow()
})

test('if reportTargetAuthTokenVar set without reportTargetAuthHeaderName', () => {
const project = new TestProject()

const telemetryOptions = {
reportTargetUrl: 'http://localhost:3000/telemetry',
reportTargetAuthTokenVar: 'auth',
}

expect(() => setupTelemetry(project, {telemetryOptions})).toThrow()
})
})

test('sets up telemetry if requested in options', () => {
const project = new TestProject({})
const telemetryOptions = {reportTargetUrl: 'http://localhost:3000/telemetry'}
Expand Down Expand Up @@ -55,6 +92,26 @@ describe('setupTelemetry function', () => {
})
})

describe('TelemetryWorkflow', () => {
test('throws for projects other than NodeProject', () => {
const project = new JavaProject({name: 'java', groupId: 'java', artifactId: 'app', version: '0', github: true})
expect(() => new TelemetryWorkflow(project.github!)).toThrow()
})

test('throws for projects without projenrc file', () => {
const parent = new TestProject()
const subproject = new TestProject({parent, name: 'subproject', outdir: 'sub'})
expect(() => new TelemetryWorkflow(subproject.github!)).toThrow()
})

test('is not added to projects without github', () => {
const project = new TestProject({github: false})
TelemetryWorkflow.addToProject(project, {})
const snapshot = synthSnapshot(project)
expect(snapshot[telemetryWorkflowPath]).not.toBeDefined()
})
})

class TestProject extends NodeProject implements IWithTelemetryReportUrl {
readonly reportTargetUrl?: string
readonly reportTargetAuthHeaderName?: string
Expand Down
Loading

0 comments on commit a88aeac

Please sign in to comment.