import type { IQService, IScope } from 'angular'; import { mock } from 'angular'; import { shallow } from 'enzyme'; import React from 'react'; import type { ICreatePipelineModalProps } from './CreatePipelineModal'; import { CreatePipelineModal } from './CreatePipelineModal'; import type { Application } from '../../application/application.model'; import { ApplicationModelBuilder } from '../../application/applicationModel.builder'; import { PipelineConfigService } from '../config/services/PipelineConfigService'; import { SETTINGS } from '../../config/settings'; import { PipelineTemplateReader } from '../config/templates/PipelineTemplateReader'; import type { IPipeline } from '../../domain'; // Disable CreatePipelineModal tests until enzyme supports React 16 https://github.com/airbnb/enzyme/issues/1553 // CreatePipelineModal uses Overridable() which uses React.forwardRef xdescribe('CreatePipelineModal', () => { let $q: IQService; let $scope: IScope; let application: Application; let initializeComponent: (configs?: Array>) => void; let component: CreatePipelineModal; beforeEach( mock.inject((_$q_: IQService, $rootScope: IScope) => { $q = _$q_; $scope = $rootScope.$new(); initializeComponent = (configs = []) => { application = ApplicationModelBuilder.createApplicationForTests( 'app', { key: 'pipelineConfigs', lazy: true, loader: () => $q.resolve(application.pipelineConfigs.data), onLoad: (_app, data) => $q.resolve(data), defaultData: [], }, { key: 'strategyConfigs', lazy: true, loader: () => $q.resolve(application.strategyConfigs.data), onLoad: (_app, data) => $q.resolve(data), defaultData: [], }, ); application.pipelineConfigs.data = configs; const props: ICreatePipelineModalProps = { application, show: true, showCallback: (): void => null, pipelineSavedCallback: (): void => null, }; const RealCreatePipelineModal = (CreatePipelineModal as any).OriginalComponent as React.ComponentType; component = shallow().instance() as CreatePipelineModal; }; }), ); describe('config instantiation', () => { it('provides a default value when no configs exist', () => { initializeComponent(); const config = component.state.configs[0]; expect(component.state.configs.length).toBe(1); expect(config.name).toBe('None'); expect(config.application).toBe('app'); expect(config.triggers).toEqual([]); expect(config.stages).toEqual([]); }); it('includes the default value when configs exist', () => { initializeComponent([{ name: 'some pipeline' }]); expect(component.state.configs.length).toBe(2); expect(component.state.configs[0].name).toBe('None'); expect(component.state.configs[1].name).toBe('some pipeline'); }); it('initializes command with the default config', () => { initializeComponent([{ name: 'some pipeline' }]); expect(component.state.configs.length).toBe(2); expect(component.state.configs[0].name).toBe('None'); expect(component.state.configs[1].name).toBe('some pipeline'); expect(component.state.command.config.name).toBe('None'); }); it(`includes all config names in the component's state to be used to determine if a name is unique`, () => { initializeComponent([{ name: 'a' }, { name: 'b' }]); expect(component.state.configs.length).toBe(3); expect(component.state.existingNames).toEqual(['None', 'a', 'b']); }); }); describe('template initialization', () => { beforeEach(() => (SETTINGS.feature.pipelineTemplates = true)); afterEach(SETTINGS.resetToOriginal); it('loads pipeline templates', () => { spyOn(PipelineTemplateReader, 'getPipelineTemplatesByScopes').and.callFake(() => { const templates = [ { id: 'templateA', scopes: ['global'], }, { id: 'templateB', scopes: ['myApp'], }, ] as any; return $q.resolve(templates); }); component.loadPipelineTemplates(); $scope.$digest(); expect(component.state.templates.map((t) => t.id)).toEqual(['templateA', 'templateB']); }); it('sets error flag, message when load is rejected', () => { spyOn(PipelineTemplateReader, 'getPipelineTemplatesByScopes').and.callFake(() => { return $q.reject(null); }); component.loadPipelineTemplates(); $scope.$digest(); expect(component.state.loadError).toEqual(true); expect(component.state.loadErrorMessage).toEqual('No message provided'); }); }); describe('pipeline name validation', () => { const setPipelineName = (_component: CreatePipelineModal, name: string): void => { _component.setState({ command: { ..._component.state.command, name } }); }; it('verifies that the pipeline name does not contain invalid characters', () => { initializeComponent(); setPipelineName(component, '\\'); expect(component.validateNameCharacters()).toEqual(false); setPipelineName(component, '^'); expect(component.validateNameCharacters()).toEqual(false); setPipelineName(component, '?'); expect(component.validateNameCharacters()).toEqual(false); setPipelineName(component, '%'); expect(component.validateNameCharacters()).toEqual(false); setPipelineName(component, '#'); expect(component.validateNameCharacters()).toEqual(false); setPipelineName(component, 'validName'); expect(component.validateNameCharacters()).toEqual(true); }); it('verifies that the pipeline name is unique', () => { initializeComponent([{ name: 'a' }, { name: 'b' }]); setPipelineName(component, 'a'); expect(component.validateNameIsUnique()).toEqual(false); setPipelineName(component, 'b'); expect(component.validateNameIsUnique()).toEqual(false); setPipelineName(component, 'c'); expect(component.validateNameIsUnique()).toEqual(true); }); }); describe('pipeline submission', () => { it('saves pipeline, adds it to application', () => { initializeComponent(); let submitted: IPipeline = null; spyOn(application.pipelineConfigs, 'refresh').and.callFake(() => { application.pipelineConfigs.data = [{ name: 'new pipeline', id: '1234-5678' }]; return $q.when(null); }); spyOn(PipelineConfigService, 'savePipeline').and.callFake((pipeline: IPipeline) => { submitted = pipeline; return $q.when(null); }); component.setState({ command: { ...component.state.command, name: 'new pipeline' } }); component.submit(); $scope.$digest(); expect(submitted.name).toBe('new pipeline'); expect(submitted.application).toBe('app'); expect(submitted.stages).toEqual([]); expect(submitted.triggers).toEqual([]); }); it('uses copy of plain version of pipeline', () => { let submitted: IPipeline = null; const toCopy = { application: 'the_app', name: 'old_name', triggers: [{ name: 'the_trigger', enabled: true, type: 'git' }], }; initializeComponent([toCopy]); spyOn(application.pipelineConfigs, 'refresh').and.callFake(() => { application.pipelineConfigs.data = [{ name: 'new pipeline', id: '1234-5678' }]; return $q.when(null); }); spyOn(PipelineConfigService, 'savePipeline').and.callFake((pipeline: IPipeline) => { submitted = pipeline; return $q.when(null); }); component.state.command.name = 'new pipeline'; component.state.command.config = toCopy; component.submit(); $scope.$digest(); expect(submitted.name).toBe('new pipeline'); expect(submitted.application).toBe('the_app'); expect(submitted.triggers.length).toBe(1); }); it('should insert new pipeline as last one in application and set its index', () => { let submitted: IPipeline = null; initializeComponent([{ name: 'x' }]); spyOn(application.pipelineConfigs, 'refresh').and.callFake(() => { application.pipelineConfigs.data = [{ name: 'new pipeline', id: '1234-5678' }]; return $q.when(null); }); spyOn(PipelineConfigService, 'savePipeline').and.callFake((pipeline: IPipeline) => { submitted = pipeline; return $q.when(null); }); component.state.command.name = 'new pipeline'; component.submit(); $scope.$digest(); expect(submitted.index).toBe(1); }); it('sets error flag, message when save is rejected', () => { initializeComponent(); spyOn(PipelineConfigService, 'savePipeline').and.callFake(() => { return $q.reject({ data: { message: 'something went wrong' } }); }); component.submit(); $scope.$digest(); expect(component.state.saveError).toBe(true); expect(component.state.saveErrorMessage).toBe('something went wrong'); }); it('provides default error message when none provided on failed save', () => { initializeComponent(); spyOn(PipelineConfigService, 'savePipeline').and.callFake(() => { return $q.reject({}); }); component.submit(); $scope.$digest(); expect(component.state.saveError).toBe(true); expect(component.state.saveErrorMessage).toBe('No message provided'); }); }); });