mirror of https://github.com/Squidex/squidex.git
28 changed files with 311 additions and 1244 deletions
@ -0,0 +1,134 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { ImmutableArray } from './../'; |
|||
|
|||
describe('ImmutableArray', () => { |
|||
it('should create empty instance', () => { |
|||
const array_1 = ImmutableArray.of(); |
|||
|
|||
expect(array_1.length).toBe(0); |
|||
}); |
|||
|
|||
it('should create same instance for empty arrays', () => { |
|||
const array_a = ImmutableArray.of(); |
|||
const array_b = ImmutableArray.of(); |
|||
const array_c = ImmutableArray.of([]); |
|||
const array_d = ImmutableArray.empty(); |
|||
|
|||
expect(array_b).toBe(array_a); |
|||
expect(array_c).toBe(array_a); |
|||
expect(array_d).toBe(array_a); |
|||
}); |
|||
|
|||
it('should create non empty instance', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
|
|||
expect(array_1.length).toBe(3); |
|||
expect(array_1.values).toEqual([1, 2, 3]); |
|||
}); |
|||
|
|||
it('should push items', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.push(4, 5); |
|||
|
|||
expect(array_2.length).toBe(5); |
|||
expect(array_2.values).toEqual([1, 2, 3, 4, 5]); |
|||
}); |
|||
|
|||
it('should return same array if pushing zero items', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.push(); |
|||
|
|||
expect(array_2).toBe(array_1); |
|||
}); |
|||
|
|||
it('should remove item', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.remove(2); |
|||
|
|||
expect(array_2.length).toBe(2); |
|||
expect(array_2.values).toEqual([1, 3]); |
|||
}); |
|||
|
|||
it('should return same array if removing zero items', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.remove(); |
|||
|
|||
expect(array_2).toBe(array_1); |
|||
}); |
|||
|
|||
it('should remove all by predicate', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.removeAll((i: number) => i % 2 === 0); |
|||
|
|||
expect(array_2.values).toEqual([1, 3]); |
|||
}); |
|||
|
|||
it('should return original if nothing has been removed', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.removeAll((i: number) => i % 200 === 0); |
|||
|
|||
expect(array_2).toEqual(array_1); |
|||
}); |
|||
|
|||
it('should replace item if found', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.replace(2, 4); |
|||
|
|||
expect(array_2.values).toEqual([1, 4, 3]); |
|||
}); |
|||
|
|||
it('should not replace item if not found', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3]); |
|||
const array_2 = array_1.replace(5, 5); |
|||
|
|||
expect(array_2).toBe(array_1); |
|||
}); |
|||
|
|||
it('should replace all by predicate', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const array_2 = array_1.replaceAll((i: number) => i % 2 === 0, i => i * 2); |
|||
|
|||
expect(array_2.values).toEqual([1, 4, 3, 8]); |
|||
}); |
|||
|
|||
it('should return original if nothing has been replace', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const array_2 = array_1.replaceAll((i: number) => i % 200 === 0, i => i); |
|||
|
|||
expect(array_2).toBe(array_1); |
|||
}); |
|||
|
|||
it('should filter items', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const array_2 = array_1.filter((i: number) => i % 2 === 0); |
|||
|
|||
expect(array_2.values).toEqual([2, 4]); |
|||
}); |
|||
|
|||
it('should map items', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const array_2 = array_1.map((i: number) => i * 2); |
|||
|
|||
expect(array_2.values).toEqual([2, 4, 6, 8]); |
|||
}); |
|||
|
|||
it('should find item', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const result = array_1.find(i => i >= 2.5); |
|||
|
|||
expect(result).toEqual(3); |
|||
}); |
|||
|
|||
it('should not return item if not found', () => { |
|||
const array_1 = ImmutableArray.of([1, 2, 3, 4]); |
|||
const result = array_1.find(i => i >= 4.5); |
|||
|
|||
expect(result).toBeUndefined(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,136 @@ |
|||
|
|||
function freeze<T>(items: T[]): T[] { |
|||
for (let item of items) { |
|||
Object.freeze(item); |
|||
} |
|||
|
|||
return items; |
|||
} |
|||
|
|||
export class ImmutableArray<T> implements Iterable<T> { |
|||
private static readonly EMPTY = new ImmutableArray<any>([]); |
|||
private readonly items: T[]; |
|||
|
|||
public [Symbol.iterator](): Iterator<T> { |
|||
return this.items.values(); |
|||
} |
|||
|
|||
public get length() { |
|||
return this.items.length; |
|||
} |
|||
|
|||
public get values(): T[] { |
|||
return [...this.items]; |
|||
} |
|||
|
|||
private constructor(items: T[]) { |
|||
this.items = items; |
|||
} |
|||
|
|||
public static empty<T>(): ImmutableArray<T> { |
|||
return ImmutableArray.EMPTY; |
|||
} |
|||
|
|||
public static of<T>(items?: T[]): ImmutableArray<T> { |
|||
if (!items || items.length === 0) { |
|||
return ImmutableArray.EMPTY; |
|||
} else { |
|||
return new ImmutableArray<T>(freeze([...items])); |
|||
} |
|||
} |
|||
|
|||
public map<R>(projection: (item: T) => R): ImmutableArray<R> { |
|||
return new ImmutableArray<R>(freeze(this.items.map(v => projection(v!)))); |
|||
} |
|||
|
|||
public filter(predicate: (item: T) => boolean): ImmutableArray<T> { |
|||
return new ImmutableArray<T>(this.items.filter(v => predicate(v!))); |
|||
} |
|||
|
|||
public find(predicate: (item: T, index: number) => boolean): T { |
|||
return this.items.find(predicate); |
|||
} |
|||
|
|||
public push(...items: T[]): ImmutableArray<T> { |
|||
if (!items || items.length === 0) { |
|||
return this; |
|||
} |
|||
return new ImmutableArray<T>([...this.items, ...freeze(items)]); |
|||
} |
|||
|
|||
public remove(...items: T[]): ImmutableArray<T> { |
|||
if (!items || items.length === 0) { |
|||
return this; |
|||
} |
|||
|
|||
const copy = this.items.slice(); |
|||
|
|||
for (let item of items) { |
|||
const index = copy.indexOf(item); |
|||
|
|||
if (index >= 0) { |
|||
copy.splice(index, 1); |
|||
} |
|||
} |
|||
|
|||
return new ImmutableArray<T>(copy); |
|||
} |
|||
|
|||
public removeAll(predicate: (item: T, index: number) => boolean): ImmutableArray<T> { |
|||
const copy = this.items.slice(); |
|||
|
|||
let hasChange = false; |
|||
|
|||
for (let i = 0; i < copy.length; ) { |
|||
if (predicate(copy[i], i)) { |
|||
copy.splice(i, 1); |
|||
|
|||
hasChange = true; |
|||
} else { |
|||
++i; |
|||
} |
|||
} |
|||
|
|||
return hasChange ? new ImmutableArray<T>(copy) : this; |
|||
} |
|||
|
|||
public replace(oldItem: T, newItem: T): ImmutableArray<T> { |
|||
const index = this.items.indexOf(oldItem); |
|||
|
|||
if (index >= 0) { |
|||
if (newItem) { |
|||
Object.freeze(newItem); |
|||
} |
|||
|
|||
const copy = [...this.items.slice(0, index), newItem, ...this.items.slice(index + 1)]; |
|||
|
|||
return new ImmutableArray<T>(copy); |
|||
} else { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
public replaceAll(predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): ImmutableArray<T> { |
|||
const copy = this.items.slice(); |
|||
|
|||
let hasChange = false; |
|||
|
|||
for (let i = 0; i < copy.length; i++) { |
|||
if (predicate(copy[i], i)) { |
|||
const newItem = replacer(copy[i]); |
|||
|
|||
if (newItem) { |
|||
Object.freeze(newItem); |
|||
} |
|||
|
|||
if (copy[i] !== newItem) { |
|||
copy[i] = newItem; |
|||
|
|||
hasChange = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return hasChange ? new ImmutableArray<T>(copy) : this; |
|||
} |
|||
} |
|||
@ -1,281 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { ImmutableIdMap } from './../'; |
|||
|
|||
class MockupData { |
|||
public readonly id: string; |
|||
|
|||
constructor(public readonly value: string | null, id?: string) { |
|||
this.id = id || value!; |
|||
} |
|||
} |
|||
|
|||
describe('ImmutableIdMap', () => { |
|||
const v1 = new MockupData('value1'); |
|||
const v2 = new MockupData('value2'); |
|||
const v3 = new MockupData('value3'); |
|||
const v4 = new MockupData('value4'); |
|||
const v5 = new MockupData('value5'); |
|||
const v6 = new MockupData('value6'); |
|||
|
|||
it('should instantiate without arguments', () => { |
|||
const list = new ImmutableIdMap<MockupData>(); |
|||
|
|||
expect(list).toBeDefined(); |
|||
}); |
|||
|
|||
it('should instantiate from array of items', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>([v1, v2, v3]); |
|||
|
|||
expect(list_1.size).toBe(3); |
|||
expect(list_1.get('value1')).toBe(v1); |
|||
expect(list_1.get('value2')).toBe(v2); |
|||
expect(list_1.get('value3')).toBe(v3); |
|||
|
|||
expect(list_1.first).toBe(v1); |
|||
expect(list_1.last).toBe(v3); |
|||
}); |
|||
|
|||
it('should add values to list', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.add(v3); |
|||
|
|||
expect(list_4.size).toBe(3); |
|||
expect(list_4.get('value1')).toBe(v1); |
|||
expect(list_4.get('value2')).toBe(v2); |
|||
expect(list_4.get('value3')).toBe(v3); |
|||
}); |
|||
|
|||
it('should convert to array', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.add(v3); |
|||
|
|||
const items = list_4.toArray(); |
|||
|
|||
expect(items.length).toBe(3); |
|||
expect(items[0]).toBe(v1); |
|||
expect(items[1]).toBe(v2); |
|||
expect(items[2]).toBe(v3); |
|||
}); |
|||
|
|||
it('should return original list when value to add has no id', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(new MockupData(null)); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return original list when value to add is null', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(null!); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return original list when item to add has already been added', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v1); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should update item', () => { |
|||
const newValue = new MockupData(v1.value, v1.id); |
|||
|
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1.id, t => newValue); |
|||
|
|||
expect(list_3.size).toBe(1); |
|||
expect(list_3.get('value1')).toBe(newValue); |
|||
}); |
|||
|
|||
it('should return undefined for invalid id', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
|
|||
expect(list_1.get(null!)).toBeUndefined(); |
|||
}); |
|||
|
|||
it('should return original list when id to update is null', () => { |
|||
const newValue = new MockupData(v1.value, v1.id); |
|||
|
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(null!, t => newValue); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when id to update does not exists', () => { |
|||
const newValue = new MockupData(v1.value, v1.id); |
|||
|
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update('unknown', t => newValue); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when updater is null', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1.id, null!); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when updater returns same item', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1.id, t => t); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when updater returns item with another id', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1.id, t => v2); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should remove values from list', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.remove('value1'); |
|||
|
|||
expect(list_4.size).toBe(1); |
|||
expect(list_4.get('value2')).toBe(v2); |
|||
expect(list_4.contains(v1.id)).toBeFalsy(); |
|||
}); |
|||
|
|||
it('should return original list when id to remove is null', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.remove(v1.id, null!); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when id to remove does not exists', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.remove(v1.id, 'unknown'); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should bring to front', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringToFront([v3.id, v5.id]); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v1, v2, v4, v6, v3, v5].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should bring forwards', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringForwards([v3.id, v4.id]); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v1, v2, v5, v3, v4, v6].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should send to back', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendToBack([v3.id, v5.id]); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v3, v5, v1, v2, v4, v6].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should send backwards', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards([v3.id, v5.id]); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v1, v3, v5, v2, v4, v6].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should move item', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.moveTo([v4.id], 1); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v1, v4, v2, v3, v5, v6].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should ignore items that are not found', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringToFront([v3.id, 'not found']); |
|||
|
|||
expect(list_2.map(t => t.id)).toEqual([v1, v2, v4, v5, v6, v3].map(t => t.id)); |
|||
}); |
|||
|
|||
it('should return original list no id found', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards(['not found', 'other not found']); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return original list when ids is null', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards(null!); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return correct result for map', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
const result = list_1.map(t => t.id); |
|||
|
|||
expect(result).toEqual(['value1', 'value2', 'value3', 'value4', 'value5', 'value6']); |
|||
}); |
|||
|
|||
it('should return correct result for forEach', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
const result: string[] = []; |
|||
|
|||
list_1.forEach(t => result.push(t.id)); |
|||
|
|||
expect(result).toEqual(['value1', 'value2', 'value3', 'value4', 'value5', 'value6']); |
|||
}); |
|||
|
|||
it('should return correct result for filter', () => { |
|||
const list_1 = new ImmutableIdMap<MockupData>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
let i = 0; |
|||
|
|||
const result = list_1.filter(t => { i++; return i % 2 === 1; }); |
|||
|
|||
expect(result).toEqual([v1, v3, v5]); |
|||
}); |
|||
|
|||
it('should add and remove large item set', () => { |
|||
const size = 1000; |
|||
const items: MockupData[] = []; |
|||
|
|||
for (let i = 0; i < size; i++) { |
|||
items.push(new MockupData(`id${i}`)); |
|||
} |
|||
|
|||
const list_1 = new ImmutableIdMap<MockupData>(); |
|||
const list_2 = list_1.add(...items); |
|||
|
|||
expect(list_2.toArray()).toEqual(items); |
|||
|
|||
const list_3 = list_2.remove(...items.map(t => t.id)); |
|||
|
|||
expect(list_3.size).toEqual(0); |
|||
}); |
|||
}); |
|||
@ -1,218 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Immutable from 'immutable'; |
|||
|
|||
export interface WithId { |
|||
id: string; |
|||
} |
|||
|
|||
export class ImmutableIdMap<T extends WithId> { |
|||
private readonly items: Immutable.OrderedMap<string, T>; |
|||
|
|||
public get size(): number { |
|||
return this.items.size; |
|||
} |
|||
|
|||
public get first(): T { |
|||
return this.items.first(); |
|||
} |
|||
|
|||
public get last(): T { |
|||
return this.items.last(); |
|||
} |
|||
|
|||
constructor(items?: T[] | Immutable.OrderedMap<string, T>) { |
|||
if (Array.isArray(items)) { |
|||
this.items = Immutable.OrderedMap<string, T>(items.map(x => [x.id, x])); |
|||
} else { |
|||
this.items = items || Immutable.OrderedMap<string, T>(); |
|||
} |
|||
|
|||
Object.freeze(this); |
|||
} |
|||
|
|||
public get(key: string): T | undefined { |
|||
return this.items.get(key, undefined); |
|||
} |
|||
|
|||
public contains(id: string): boolean { |
|||
return !!this.items.get(id); |
|||
} |
|||
|
|||
public toArray(): T[] { |
|||
return this.items.toArray(); |
|||
} |
|||
|
|||
public map<R>(projection: (item: T, key?: string) => R): R[] { |
|||
return this.items.map((v, k) => projection(v!, k)).toArray(); |
|||
} |
|||
|
|||
public filter(projection: (item: T, key?: string) => boolean): T[] { |
|||
return this.items.filter((v, k) => projection(v!, k)).toArray(); |
|||
} |
|||
|
|||
public forEach(projection: (item: T, key?: string) => void): void { |
|||
this.items.forEach((v, k) => projection(v!, k)); |
|||
} |
|||
|
|||
public add(...items: T[]): ImmutableIdMap<T> { |
|||
for (let item of items) { |
|||
if (!item || !item.id || this.get(item.id)) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
let newItems = this.items; |
|||
|
|||
if (items.length > 50) { |
|||
newItems = this.items.withMutations(mutable => { |
|||
for (let item of items) { |
|||
mutable.set(item.id, item); |
|||
} |
|||
}); |
|||
} else { |
|||
for (let item of items) { |
|||
newItems = newItems.set(item.id, item); |
|||
} |
|||
} |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public remove(...ids: string[]): ImmutableIdMap<T> { |
|||
for (let id of ids) { |
|||
if (!id || !this.get(id)) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
let newItems = this.items; |
|||
|
|||
if (ids.length > 50) { |
|||
newItems = this.items.withMutations(mutable => { |
|||
for (let id of ids) { |
|||
mutable.remove(id); |
|||
} |
|||
}); |
|||
} else { |
|||
for (let id of ids) { |
|||
newItems = newItems.remove(id); |
|||
} |
|||
} |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public update(id: string, updater: (item: T) => T): ImmutableIdMap<T> { |
|||
const oldItem = this.items.get(id); |
|||
|
|||
if (!oldItem || !updater) { |
|||
return this; |
|||
} |
|||
|
|||
const newItem = updater(oldItem); |
|||
|
|||
if (!newItem || newItem === oldItem || newItem.id !== oldItem.id) { |
|||
return this; |
|||
} |
|||
|
|||
const newItems = this.items.set(id, newItem); |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public bringToFront(ids: string[]): ImmutableIdMap<T> { |
|||
return this.moveTo(ids, Number.MAX_VALUE); |
|||
} |
|||
|
|||
public bringForwards(ids: string[]): ImmutableIdMap<T> { |
|||
return this.moveTo(ids, 1, true); |
|||
} |
|||
|
|||
public sendBackwards(ids: string[]): ImmutableIdMap<T> { |
|||
return this.moveTo(ids, -1, true); |
|||
} |
|||
|
|||
public sendToBack(ids: string[]): ImmutableIdMap<T> { |
|||
return this.moveTo(ids, 0); |
|||
} |
|||
|
|||
public moveTo(ids: string[], target: number, relative = false): ImmutableIdMap<T> { |
|||
const itemsToStay: ItemToSort<T>[] = []; |
|||
const itemsToMove: ItemToSort<T>[] = []; |
|||
|
|||
this.items.toArray().forEach((item: T, index: number) => { |
|||
const itemToAdd: ItemToSort<T> = { isInIds: ids && ids.indexOf(item.id) >= 0, index: index, value: item }; |
|||
|
|||
if (itemToAdd.isInIds) { |
|||
itemsToMove.push(itemToAdd); |
|||
} else { |
|||
itemsToStay.push(itemToAdd); |
|||
} |
|||
}); |
|||
|
|||
if (itemsToMove.length === 0) { |
|||
return this; |
|||
} |
|||
|
|||
let isBackwards = false, newIndex = 0; |
|||
|
|||
if (relative) { |
|||
isBackwards = target <= 0; |
|||
|
|||
let currentIndex = |
|||
target > 0 ? |
|||
Number.MIN_VALUE : |
|||
Number.MAX_VALUE; |
|||
|
|||
for (let itemFromIds of itemsToMove) { |
|||
if (target > 0) { |
|||
currentIndex = Math.max(itemFromIds.index, currentIndex); |
|||
} else { |
|||
currentIndex = Math.min(itemFromIds.index, currentIndex); |
|||
} |
|||
} |
|||
|
|||
newIndex = currentIndex + target; |
|||
} else { |
|||
newIndex = target; |
|||
|
|||
if (itemsToMove[0].index > newIndex) { |
|||
isBackwards = true; |
|||
} |
|||
} |
|||
|
|||
const result: any[][] = []; |
|||
|
|||
for (let item of itemsToStay) { |
|||
if ((isBackwards && item.index >= newIndex) || item.index > newIndex) { |
|||
break; |
|||
} |
|||
|
|||
result.push([item.value.id, item.value]); |
|||
} |
|||
|
|||
for (let item of itemsToMove) { |
|||
result.push([item.value.id, item.value]); |
|||
} |
|||
|
|||
for (let item of itemsToStay) { |
|||
if ((isBackwards && item.index >= newIndex) || item.index > newIndex) { |
|||
result.push([item.value.id, item.value]); |
|||
} |
|||
} |
|||
|
|||
return this.cloned(Immutable.OrderedMap<string, T>(result)); |
|||
} |
|||
|
|||
private cloned(items: Immutable.OrderedMap<string, T>): ImmutableIdMap<T> { |
|||
return new ImmutableIdMap<T>(items); |
|||
} |
|||
} |
|||
|
|||
interface ItemToSort<T> { isInIds: boolean; index: number; value: T; } |
|||
@ -1,238 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { ImmutableList } from './../'; |
|||
|
|||
describe('ImmutableList', () => { |
|||
const v1 = 'value1'; |
|||
const v2 = 'value2'; |
|||
const v3 = 'value3'; |
|||
const v4 = 'value4'; |
|||
const v5 = 'value5'; |
|||
const v6 = 'value6'; |
|||
|
|||
it('should instantiate without arguments', () => { |
|||
const list = new ImmutableList<string>(); |
|||
|
|||
expect(list).toBeDefined(); |
|||
}); |
|||
|
|||
it('should instantiate from array of items', () => { |
|||
const list_1 = new ImmutableList<string>([v1, v2, v3]); |
|||
|
|||
expect(list_1.size).toBe(3); |
|||
expect(list_1.get(0)).toBe(v1); |
|||
expect(list_1.get(1)).toBe(v2); |
|||
expect(list_1.get(2)).toBe(v3); |
|||
}); |
|||
|
|||
it('should add values to list', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.add(v3); |
|||
|
|||
expect(list_4.size).toBe(3); |
|||
expect(list_4.get(0)).toBe(v1); |
|||
expect(list_4.get(1)).toBe(v2); |
|||
expect(list_4.get(2)).toBe(v3); |
|||
}); |
|||
|
|||
it('should convert to array', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.add(v3); |
|||
|
|||
const items = list_4.toArray(); |
|||
|
|||
expect(items.length).toBe(3); |
|||
expect(items[0]).toBe(v1); |
|||
expect(items[1]).toBe(v2); |
|||
expect(items[2]).toBe(v3); |
|||
}); |
|||
|
|||
it('should return original list when value to add is null', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(null!); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should update item', () => { |
|||
const newValue = 'v1New'; |
|||
|
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1, t => newValue); |
|||
|
|||
expect(list_3.size).toBe(1); |
|||
expect(list_3.get(0)).toBe(newValue); |
|||
}); |
|||
|
|||
it('should return undefined for invalid index', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
|
|||
expect(list_2.get(-10)).toBeUndefined(); |
|||
expect(list_2.get(100)).toBeUndefined(); |
|||
}); |
|||
|
|||
it('should return original list when item to update is null', () => { |
|||
const newValue = 'v1New'; |
|||
|
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(null!, t => newValue); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when updater is null', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1, null!); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when updater returns same item', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.update(v1, t => t); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should remove values from list', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.add(v2); |
|||
const list_4 = list_3.remove(v1); |
|||
|
|||
expect(list_4.size).toBe(1); |
|||
expect(list_4.get(0)).toBe(v2); |
|||
}); |
|||
|
|||
it('should return original list when item to remove is null', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.remove(null!); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should return original list when item to remove does not exists', () => { |
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(v1); |
|||
const list_3 = list_2.remove(v4); |
|||
|
|||
expect(list_3).toBe(list_2); |
|||
}); |
|||
|
|||
it('should bring to front', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringToFront([v3, v5]); |
|||
|
|||
expect(list_2.toArray()).toEqual([v1, v2, v4, v6, v3, v5]); |
|||
}); |
|||
|
|||
it('should bring forwards', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringForwards([v3, v4]); |
|||
|
|||
expect(list_2.toArray()).toEqual([v1, v2, v5, v3, v4, v6]); |
|||
}); |
|||
|
|||
it('should send to back', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendToBack([v3, v5]); |
|||
|
|||
expect(list_2.toArray()).toEqual([v3, v5, v1, v2, v4, v6]); |
|||
}); |
|||
|
|||
it('should send backwards', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards([v3, v5]); |
|||
|
|||
expect(list_2.toArray()).toEqual([v1, v3, v5, v2, v4, v6]); |
|||
}); |
|||
|
|||
it('should move item', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.moveTo([v4], 1); |
|||
|
|||
expect(list_2.toArray()).toEqual([v1, v4, v2, v3, v5, v6]); |
|||
}); |
|||
|
|||
it('should ignore items that are not found', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.bringToFront([v3, 'not found']); |
|||
|
|||
expect(list_2.toArray()).toEqual([v1, v2, v4, v5, v6, v3]); |
|||
}); |
|||
|
|||
it('should return original list no id found', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards(['not found']); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return original list when ids is null', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
const list_2 = list_1.sendBackwards(null!); |
|||
|
|||
expect(list_2).toBe(list_1); |
|||
}); |
|||
|
|||
it('should return correct result for map', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
const result = list_1.map(t => 'x_' + t); |
|||
|
|||
expect(result).toEqual(['x_value1', 'x_value2', 'x_value3', 'x_value4', 'x_value5', 'x_value6']); |
|||
}); |
|||
|
|||
it('should return correct result for forEach', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
const result: string[] = []; |
|||
|
|||
list_1.forEach(t => result.push('x_' + t)); |
|||
|
|||
expect(result).toEqual(['x_value1', 'x_value2', 'x_value3', 'x_value4', 'x_value5', 'x_value6']); |
|||
}); |
|||
|
|||
it('should return correct result for filter', () => { |
|||
const list_1 = new ImmutableList<string>().add(v1, v2, v3, v4, v5, v6); |
|||
|
|||
let i = 0; |
|||
|
|||
const result = list_1.filter(t => { i++; return i % 2 === 1; }); |
|||
|
|||
expect(result).toEqual([v1, v3, v5]); |
|||
}); |
|||
|
|||
it('should add and remove large item set', () => { |
|||
const size = 1000; |
|||
const items: string[] = []; |
|||
|
|||
for (let i = 0; i < size; i++) { |
|||
items.push('id' + i); |
|||
} |
|||
|
|||
const list_1 = new ImmutableList<string>(); |
|||
const list_2 = list_1.add(...items); |
|||
|
|||
expect(list_2.toArray()).toEqual(items); |
|||
|
|||
const list_3 = list_2.remove(...items); |
|||
|
|||
expect(list_3.size).toEqual(0); |
|||
}); |
|||
}); |
|||
@ -1,210 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Immutable from 'immutable'; |
|||
|
|||
export class ImmutableList<T> implements Iterable<T> { |
|||
private readonly items: Immutable.List<T>; |
|||
|
|||
public [Symbol.iterator](): Iterator<T> { |
|||
return this.items.values(); |
|||
} |
|||
|
|||
public get size(): number { |
|||
return this.items.size; |
|||
} |
|||
|
|||
constructor(items?: T[] | Immutable.List<T>) { |
|||
if (Array.isArray(items)) { |
|||
this.items = Immutable.List<T>(items); |
|||
} else if (items) { |
|||
this.items = items || Immutable.List<T>(); |
|||
} |
|||
|
|||
Object.freeze(this); |
|||
} |
|||
|
|||
public get(index: number): T | undefined { |
|||
return this.items.get(index, undefined); |
|||
} |
|||
|
|||
public toArray(): T[] { |
|||
return this.items.toArray(); |
|||
} |
|||
|
|||
public map<R>(projection: (item: T) => R): ImmutableList<R> { |
|||
return new ImmutableList<R>(this.items.map(v => projection(v!)).toArray()); |
|||
} |
|||
|
|||
public filter(projection: (item: T) => boolean): T[] { |
|||
return this.items.filter(v => projection(v!)).toArray(); |
|||
} |
|||
|
|||
public forEach(projection: (item: T) => void): void { |
|||
this.items.forEach(v => projection(v!)); |
|||
} |
|||
|
|||
public add(...items: T[]): ImmutableList<T> { |
|||
for (let item of items) { |
|||
if (!item) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
let newItems = this.items; |
|||
|
|||
if (items.length > 50) { |
|||
newItems = this.items.withMutations(mutable => { |
|||
for (let item of items) { |
|||
mutable.push(item); |
|||
} |
|||
}); |
|||
} else { |
|||
for (let item of items) { |
|||
newItems = newItems.push(item); |
|||
} |
|||
} |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public remove(...items: T[]): ImmutableList<T> { |
|||
for (let item of items) { |
|||
if (!item || this.items.indexOf(item) < 0) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
let newItems = this.items; |
|||
|
|||
if (items.length > 50) { |
|||
newItems = this.items.withMutations(mutable => { |
|||
for (let item of items) { |
|||
mutable.remove(mutable.indexOf(item)); |
|||
} |
|||
}); |
|||
} else { |
|||
for (let item of items) { |
|||
newItems = newItems.remove(newItems.indexOf(item)); |
|||
} |
|||
} |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public update(item: T, updater: (item: T) => T): ImmutableList<T> { |
|||
const index = this.items.indexOf(item); |
|||
|
|||
if (index < 0 || !updater) { |
|||
return this; |
|||
} |
|||
|
|||
const newItem = updater(item); |
|||
|
|||
if (!newItem || newItem === item) { |
|||
return this; |
|||
} |
|||
|
|||
const newItems = this.items.set(index, newItem); |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public bringToFront(items: T[]): ImmutableList<T> { |
|||
return this.moveTo(items, Number.MAX_VALUE); |
|||
} |
|||
|
|||
public bringForwards(items: T[]): ImmutableList<T> { |
|||
return this.moveTo(items, 1, true); |
|||
} |
|||
|
|||
public sendBackwards(items: T[]): ImmutableList<T> { |
|||
return this.moveTo(items, -1, true); |
|||
} |
|||
|
|||
public sendToBack(items: T[]): ImmutableList<T> { |
|||
return this.moveTo(items, 0); |
|||
} |
|||
|
|||
public moveTo(items: T[], target: number, relative = false): ImmutableList<T> { |
|||
const itemsToStay: ItemToSort<T>[] = []; |
|||
const itemsToMove: ItemToSort<T>[] = []; |
|||
|
|||
const allItems = this.items.toArray(); |
|||
|
|||
for (let i = 0; i < allItems.length; i++) { |
|||
const item = allItems[i]; |
|||
|
|||
const itemToAdd: ItemToSort<T> = { isInItems: items && items.indexOf(item) >= 0, index: i, value: item }; |
|||
|
|||
if (itemToAdd.isInItems) { |
|||
itemsToMove.push(itemToAdd); |
|||
} else { |
|||
itemsToStay.push(itemToAdd); |
|||
} |
|||
} |
|||
|
|||
if (itemsToMove.length === 0) { |
|||
return this; |
|||
} |
|||
|
|||
let isBackwards = false, newIndex = 0; |
|||
|
|||
if (relative) { |
|||
isBackwards = target <= 0; |
|||
|
|||
let currentIndex = |
|||
target > 0 ? |
|||
Number.MIN_VALUE : |
|||
Number.MAX_VALUE; |
|||
|
|||
for (let itemFromIds of itemsToMove) { |
|||
if (target > 0) { |
|||
currentIndex = Math.max(itemFromIds.index, currentIndex); |
|||
} else { |
|||
currentIndex = Math.min(itemFromIds.index, currentIndex); |
|||
} |
|||
} |
|||
|
|||
newIndex = currentIndex + target; |
|||
} else { |
|||
newIndex = target; |
|||
|
|||
if (itemsToMove[0].index > newIndex) { |
|||
isBackwards = true; |
|||
} |
|||
} |
|||
|
|||
const result: T[] = []; |
|||
|
|||
for (let item of itemsToStay) { |
|||
if ((isBackwards && item.index >= newIndex) || item.index > newIndex) { |
|||
break; |
|||
} |
|||
|
|||
result.push(item.value); |
|||
} |
|||
|
|||
for (let item of itemsToMove) { |
|||
result.push(item.value); |
|||
} |
|||
|
|||
for (let item of itemsToStay) { |
|||
if ((isBackwards && item.index >= newIndex) || item.index > newIndex) { |
|||
result.push(item.value); |
|||
} |
|||
} |
|||
|
|||
return new ImmutableList<T>(result); |
|||
} |
|||
|
|||
private cloned(items: Immutable.List<T>): ImmutableList<T> { |
|||
return new ImmutableList<T>(items); |
|||
} |
|||
} |
|||
|
|||
interface ItemToSort<T> { isInItems: boolean; index: number; value: T; } |
|||
@ -1,158 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { ImmutableSet } from './../'; |
|||
|
|||
describe('ImmutableSet', () => { |
|||
it('should instantiate instance from array', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '1', '2', '3']); |
|||
|
|||
expect(set_1.size).toBe(3); |
|||
expect(set_1.contains('1')).toBeTruthy(); |
|||
expect(set_1.contains('2')).toBeTruthy(); |
|||
expect(set_1.contains('3')).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should add items', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.add('1'); |
|||
const set_4 = set_3.add('2'); |
|||
const set_5 = set_4.add('3'); |
|||
|
|||
expect(set_5.size).toBe(3); |
|||
expect(set_5.contains('1')).toBeTruthy(); |
|||
expect(set_5.contains('2')).toBeTruthy(); |
|||
expect(set_5.contains('3')).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should convert to aray', () => { |
|||
const set_1 = new ImmutableSet<string>(['a', 'b']); |
|||
|
|||
const array = set_1.toArray(); |
|||
expect(array.length).toBe(2); |
|||
expect(array.indexOf('a') >= 0).toBeTruthy(); |
|||
expect(array.indexOf('b') >= 0).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should return original set when item to add is null', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add(null!); |
|||
|
|||
expect(set_2).toBe(set_1); |
|||
}); |
|||
|
|||
it('should return original set when item to add already exists', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.add('1'); |
|||
|
|||
expect(set_3).toBe(set_2); |
|||
}); |
|||
|
|||
it('should remove item', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.remove('1'); |
|||
|
|||
expect(set_3.size).toBe(0); |
|||
}); |
|||
|
|||
it('should return original set when item to remove is not found', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.remove('unknown'); |
|||
|
|||
expect(set_3).toBe(set_2); |
|||
}); |
|||
|
|||
it('should create new set', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.set(['a', 'b']); |
|||
|
|||
expect(set_3.size).toBe(2); |
|||
expect(set_3.contains('a')).toBeTruthy(); |
|||
expect(set_3.contains('b')).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should return original set when any item to set is null', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.set(['1', null!]); |
|||
|
|||
expect(set_3).toBe(set_2); |
|||
}); |
|||
|
|||
it('should return original set when items to set is null', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.set(null!); |
|||
|
|||
expect(set_3).toBe(set_2); |
|||
}); |
|||
|
|||
it('should return original set when items is same', () => { |
|||
const set_1 = new ImmutableSet<string>(); |
|||
const set_2 = set_1.add('1'); |
|||
const set_3 = set_2.set(['a', 'b']); |
|||
const set_4 = set_3.set(['a', 'b']); |
|||
|
|||
expect(set_4).toBe(set_3); |
|||
}); |
|||
|
|||
it('should remvoe many', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
const set_2 = set_1.remove('2', '4'); |
|||
|
|||
expect(set_2.size).toBe(2); |
|||
expect(set_2.contains('1')).toBeTruthy(); |
|||
expect(set_2.contains('3')).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should return original set when any item to remove is null', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
const set_2 = set_1.remove('3', null!); |
|||
|
|||
expect(set_2).toBe(set_1); |
|||
}); |
|||
|
|||
it('should return original set when items to remove is null', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
const set_2 = set_1.remove(null!); |
|||
|
|||
expect(set_2).toBe(set_1); |
|||
}); |
|||
|
|||
it('should return correct result for map', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
|
|||
const result = set_1.map(t => t + t); |
|||
|
|||
expect(result).toEqual(['11', '22', '33', '44']); |
|||
}); |
|||
|
|||
it('should return correct result for forEach', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
|
|||
const result: string[] = []; |
|||
|
|||
set_1.forEach(t => result.push(t + t)); |
|||
|
|||
expect(result).toEqual(['11', '22', '33', '44']); |
|||
}); |
|||
|
|||
it('should return correct result for filter', () => { |
|||
const set_1 = new ImmutableSet<string>(['1', '2', '3', '4']); |
|||
|
|||
let i = 0; |
|||
|
|||
const result = set_1.filter(t => { i++; return i % 2 === 1; }); |
|||
|
|||
expect(result).toEqual(['1', '3']); |
|||
}); |
|||
}); |
|||
@ -1,96 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Immutable from 'immutable'; |
|||
|
|||
export class ImmutableSet<T> implements Iterable<T> { |
|||
private readonly items: Immutable.Set<T>; |
|||
|
|||
public get size(): number { |
|||
return this.items.size; |
|||
} |
|||
|
|||
public [Symbol.iterator](): Iterator<T> { |
|||
return this.items.values(); |
|||
} |
|||
|
|||
constructor(items?: T[] | Immutable.Set<T>) { |
|||
if (Array.isArray(items)) { |
|||
this.items = Immutable.Set<T>(items); |
|||
} else { |
|||
this.items = items || Immutable.Set<T>(); |
|||
} |
|||
|
|||
Object.freeze(this); |
|||
} |
|||
|
|||
public contains(item: T): boolean { |
|||
return this.items.contains(item); |
|||
} |
|||
|
|||
public toArray(): T[] { |
|||
return this.items.toArray(); |
|||
} |
|||
|
|||
public map<R>(projection: (item: T) => R): R[] { |
|||
return this.items.map(v => projection(v!)).toArray(); |
|||
} |
|||
|
|||
public filter(projection: (item: T) => boolean): T[] { |
|||
return this.items.filter(v => projection(v!)).toArray(); |
|||
} |
|||
|
|||
public forEach(projection: (item: T) => void): void { |
|||
this.items.forEach(v => projection(v!)); |
|||
} |
|||
|
|||
public add(item: T): ImmutableSet<T> { |
|||
if (!item) { |
|||
return this; |
|||
} |
|||
|
|||
const newItems = this.items.add(item); |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public remove(...items: T[]): ImmutableSet<T> { |
|||
for (let item of items) { |
|||
if (!item) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
const newItems = this.items.subtract(items); |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
public set(items: T[]): ImmutableSet<T> { |
|||
if (!items) { |
|||
return this; |
|||
} |
|||
|
|||
for (let item of items) { |
|||
if (!item) { |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
const newItems = this.items.intersect(items).merge(items); |
|||
|
|||
return this.cloned(newItems); |
|||
} |
|||
|
|||
private cloned(items: Immutable.Set<T>): ImmutableSet<T> { |
|||
if (items !== this.items) { |
|||
return new ImmutableSet<T>(items!); |
|||
} else { |
|||
return this; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue