Browse Source

Finalize UI with tests.

pull/380/head
Sebastian Stehle 7 years ago
parent
commit
59cfe76aa5
  1. 18
      src/Squidex/app/framework/utils/immutable-array.ts
  2. 317
      src/Squidex/app/shared/services/workflows.service.spec.ts
  3. 82
      src/Squidex/app/shared/services/workflows.service.ts

18
src/Squidex/app/framework/utils/immutable-array.ts

@ -193,25 +193,11 @@ export class ImmutableArray<T> implements Iterable<T> {
}
export function compareStringsAsc(a: string, b: string) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
return a.localeCompare(b, undefined, { sensitivity: 'base' });
}
export function compareStringsDesc(a: string, b: string) {
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
return a.localeCompare(b, undefined, { sensitivity: 'base' });
}
export function compareNumbersAsc(a: number, b: number) {

317
src/Squidex/app/shared/services/workflows.service.spec.ts

@ -11,7 +11,7 @@ describe('Workflow', () => {
it('should create empty workflow', () => {
const workflow = new WorkflowDto();
expect(workflow.name).toEqual('Default');
expect(workflow.initial);
});
it('should add step to workflow', () => {
@ -19,51 +19,68 @@ describe('Workflow', () => {
new WorkflowDto()
.setStep('1', { color: '#00ff00' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' }
],
transitions: [],
name: 'Default'
expect(workflow.serialize()).toEqual({
steps: {
'1': { transitions: {}, color: '#00ff00' }
},
initial: '1'
});
});
it('should override settings if step already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('1', { color: '#00ff00', noUpdate: true })
.setStep('1', { color: 'red' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: 'red' }
],
transitions: [],
name: 'Default'
expect(workflow.serialize()).toEqual({
steps: {
'1': { transitions: {}, color: 'red', noUpdate: true }
},
initial: '1'
});
});
it('should not remove step if locked', () => {
it('should return same workflow if step to update is locked', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00', isLocked: true })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.removeStep('1');
.setStep('1', { color: '#00ff00', isLocked: true });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00', isLocked: true },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' }
],
name: 'Default'
});
const updated = workflow.setStep('1', { color: 'red' });
expect(updated).toBe(workflow);
});
it('should sort steps case invariant', () => {
const workflow =
new WorkflowDto()
.setStep('Z')
.setStep('a');
expect(workflow.steps).toEqual([
{ name: 'a' },
{ name: 'Z' }
]);
});
it('should return same workflow if step to remove is locked', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00', isLocked: true });
const updated = workflow.removeStep('1');
expect(updated).toBe(workflow);
});
it('should return same workflow if step to remove not found', () => {
const workflow =
new WorkflowDto()
.setStep('1');
const updated = workflow.removeStep('3');
expect(updated).toBe(workflow);
});
it('should remove step', () => {
@ -77,19 +94,46 @@ describe('Workflow', () => {
.setTransition('2', '3', { expression: '2 === 3' })
.removeStep('1');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
expect(workflow.serialize()).toEqual({
steps: {
'2': {
transitions: {
'3': { expression: '2 === 3' }
},
color: '#ff0000'
},
'3': { transitions: {}, color: '#0000ff' }
},
initial: '2'
});
});
it('should make first non-locked step the initial step if initial removed', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2', { isLocked: true })
.setStep('3')
.removeStep('1');
expect(workflow.serialize()).toEqual({
steps: {
'2': { transitions: {}, isLocked: true },
'3': { transitions: {} }
},
initial: '3'
});
});
it('should unset initial step if initial removed', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.removeStep('1');
expect(workflow.serialize()).toEqual({ steps: {}, initial: undefined });
});
it('should rename step', () => {
const workflow =
new WorkflowDto()
@ -99,90 +143,157 @@ describe('Workflow', () => {
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' })
.setTransition('2', '3', { expression: '2 === 3' })
.renameStep('1', '4');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '4', color: '#00ff00' },
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '4', to: '2', expression: '1 === 2' },
{ from: '2', to: '4', expression: '2 === 1' },
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
.renameStep('1', 'a');
expect(workflow.serialize()).toEqual({
steps: {
'a': {
transitions: {
'2': { expression: '1 === 2' }
},
color: '#00ff00'
},
'2': {
transitions: {
'a': { expression: '2 === 1' },
'3': { expression: '2 === 3' }
},
color: '#ff0000'
},
'3': { transitions: {}, color: '#0000ff' }
},
initial: 'a'
});
});
it('should add transitions to workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setStep('1')
.setStep('2')
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' },
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
expect(workflow.serialize()).toEqual({
steps: {
'1': {
transitions: {
'2': { expression: '1 === 2' }
}
},
'2': {
transitions: {
'1': { expression: '2 === 1' }
}
}
},
initial: '1'
});
});
it('should add remove transition from workflow', () => {
it('should remove transition from workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 1' })
.setStep('1')
.setStep('2')
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' })
.removeTransition('1', '2');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
expect(workflow.serialize()).toEqual({
steps: {
'1': { transitions: {}},
'2': {
transitions: {
'1': { expression: '2 === 1' }
}
}
},
initial: '1'
});
});
it('should override settings if transition already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('1', '2', { expression: '1 !== 2' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 !== 2' }
],
name: 'Default'
.setStep('1')
.setStep('2')
.setTransition('2', '1', { expression: '2 === 1', role: 'Role' })
.setTransition('2', '1', { expression: '2 !== 1' });
expect(workflow.serialize()).toEqual({
steps: {
'1': { transitions: {} },
'2': {
transitions: {
'1': { expression: '2 !== 1', role: 'Role' }
}
}
},
initial: '1'
});
});
});
function simplify(value: any) {
return JSON.parse(JSON.stringify(value));
}
it('should return same workflow if transition to update not found by from step', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2')
.setTransition('1', '2');
const updated = workflow.setTransition('3', '2', { role: 'Role' });
expect(updated).toBe(workflow);
});
it('should return same workflow if transition to update not found by to step', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2')
.setTransition('1', '2');
const updated = workflow.setTransition('1', '3', { role: 'Role' });
expect(updated).toBe(workflow);
});
it('should return same workflow if transition to remove not', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2')
.setTransition('1', '2');
const updated = workflow.removeTransition('1', '3');
expect(updated).toBe(workflow);
});
it('should return same workflow if step to make initial is locked', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2', { color: '#00ff00', isLocked: true });
const updated = workflow.setInitial('2');
expect(updated).toBe(workflow);
});
it('should set initial step', () => {
const workflow =
new WorkflowDto()
.setStep('1')
.setStep('2')
.setInitial('2');
expect(workflow.serialize()).toEqual({
steps: {
'1': { transitions: {} },
'2': { transitions: {} }
},
initial: '2'
});
});
});

82
src/Squidex/app/shared/services/workflows.service.ts

@ -5,30 +5,21 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import {
compareStringsAsc,
Model,
ResourceLinks
} from '@app/framework';
import { compareStringsAsc, ResourceLinks } from '@app/framework';
export class WorkflowDto extends Model<WorkflowDto> {
export class WorkflowDto {
public readonly _links: ResourceLinks;
constructor(links: ResourceLinks = {},
public readonly name: string = 'Default',
public readonly initial?: string,
public readonly steps: WorkflowStep[] = [],
public readonly transitions: WorkflowTransition[] = [],
public readonly initial?: string
private readonly transitions: WorkflowTransition[] = []
) {
super();
this._links = links;
}
public onCloned() {
this.steps.sort((a, b) => compareStringsAsc(a.name, b.name));
this.transitions.sort((a, b) => compareStringsAsc(a.to, b.to));
this._links = links;
}
public getOpenSteps(step: WorkflowStep) {
@ -43,7 +34,7 @@ export class WorkflowDto extends Model<WorkflowDto> {
return this.steps.find(x => x.name === name)!;
}
public setStep(name: string, values: Partial<WorkflowStepValues>) {
public setStep(name: string, values: Partial<WorkflowStepValues> = {}) {
const found = this.getStep(name);
if (found) {
@ -64,28 +55,40 @@ export class WorkflowDto extends Model<WorkflowDto> {
initial = steps[0].name;
}
return this.with({ steps, initial });
return new WorkflowDto(this._links, initial, steps, this.transitions);
}
public setInitial(initial: string) {
const found = this.getStep(initial);
if (!found || initial === 'Published') {
if (!found || found.isLocked) {
return this;
}
return this.with({ initial });
return new WorkflowDto(this._links, initial, this.steps, this.transitions);
}
public removeStep(name: string) {
const steps = this.steps.filter(s => s.name !== name || s.isLocked);
if (steps.length === this.steps.length) {
return this;
}
const transitions =
steps.length !== this.steps.length ?
this.transitions.filter(t => t.from !== name && t.to !== name) :
this.transitions;
return this.with({ steps, transitions });
let initial = this.initial;
if (initial === name) {
const first = steps.find(x => !x.isLocked);
initial = first ? first.name : undefined;
}
return new WorkflowDto(this._links, initial, steps, transitions);
}
public renameStep(name: string, newName: string) {
@ -115,16 +118,26 @@ export class WorkflowDto extends Model<WorkflowDto> {
return transition;
});
return this.with({ steps, transitions });
let initial = this.initial;
if (initial === name) {
initial = newName;
}
return new WorkflowDto(this._links, initial, steps, transitions);
}
public removeTransition(from: string, to: string) {
const transitions = this.transitions.filter(t => t.from !== from || t.to !== to);
return this.with({ transitions });
if (transitions.length === this.transitions.length) {
return this;
}
return new WorkflowDto(this._links, this.initial, this.steps, transitions);
}
public setTransition(from: string, to: string, values?: Partial<WorkflowTransitionValues>) {
public setTransition(from: string, to: string, values: Partial<WorkflowTransitionValues> = {}) {
const stepFrom = this.getStep(from);
if (!stepFrom) {
@ -147,7 +160,28 @@ export class WorkflowDto extends Model<WorkflowDto> {
const transitions = [...this.transitions.filter(t => t !== found), { from, to, ...values }];
return this.with({ transitions });
return new WorkflowDto(this._links, this.initial, this.steps, transitions);
}
public serialize(): any {
const result = { steps: {}, initial: this.initial };
for (let step of this.steps) {
const { name, ...values } = step;
const s = { ...values, transitions: {} };
for (let transition of this.getTransitions(step)) {
const { from, to, step: _, ...t } = transition;
s.transitions[to] = t;
}
result.steps[name] = s;
}
return result;
}
}

Loading…
Cancel
Save