Skip to content

Instantly share code, notes, and snippets.

@joegaudet
Created February 6, 2025 06:17
Show Gist options
  • Save joegaudet/f8808f3d05f570755f6b05eb3a0a39f7 to your computer and use it in GitHub Desktop.
Save joegaudet/f8808f3d05f570755f6b05eb3a0a39f7 to your computer and use it in GitHub Desktop.

Given the following code and test, implement the ChangeSet class and attribute es6 decorator so that the tests pass. The code should be valid typescript code.

class ChangeSet {

}

class User extends ChangeSet {
@attribute name: string;
@attribute age: number;
}

describe('ChangeSet', () => {

it('should create a new instance from a hash', () => {
  const change = new ChangeSet({name: 'John', age: 25});

  expect(change.name).to.equal('John');
  expect(change.age).to.equal(25);
});

it('should track changes of the attributes', () => {
  const change = new ChangeSet({name: 'John', age: 25});

  expect(change.name.dirty).to.equal(false);
  change.name = 'Jane';
  expect(change.dirty).to.equal(true);
  expect(change.name.dirty).to.equal(true);
  expect(change.changes).to.equal({name: 'Jane'});
});

it('should allow resetting changes of an attribute', ()    => {
  const change = new ChangeSet({name: 'John', age: 25});
  change.name = 'Jane';
  expect(change.changes).to.equal({name: 'Jane'});
  change.name.reset();
  expect(change.changes).to.equal({name: 'John'});
  expect(change.dirty).to.equal(true);
});

it('should allow resetting changes of all attributes', ()    => {
  const change = new ChangeSet({name: 'John', age: 25});
  change.name = 'Jane';
  expect(change.changes).to.equal({name: 'Jane'});
  expect(change.dirty).to.equal(false);
  change.reset();
  expect(change.changes).to.equal({name: 'John'});
  expect(change.dirty).to.equal(true);
})

it('should allow enumeration of all attribute keys', ()    => {
  const change = new ChangeSet({name: 'John', age: 25});
  expect(change.attributes).to.equal(['name', 'age']);
});

it('should express its changes as a hash', ()    => {
  const change = new ChangeSet({name: 'John', age: 25});
  change.name = 'Jane';
  change.age = 25;
  expect(change.changes).to.equal({name: 'Jane', age: 30});
});

it('should allow being constructed from an array of hashes, marking attributes that are not equal amongs the hashes as mixed', () => {
  const change1 = new ChangeSet({name: 'John', age: 25});
  const change2 = new ChangeSet({name: 'Jane', age: 25});
  const change3 = ChangeSet.fromArray([change1, change2]);
  expect(change1.name.mixed).to.equal(true);
  expect(change1.age.mixed).to.equal(false);
});
});
function attribute(target: any, key: string) {
Object.defineProperty(target, key, {
get() {
return this.getAttribute(key);
},
set(value) {
this.setAttribute(key, value);
},
enumerable: true,
configurable: true
});
}
class ChangeSet {
private _original: Record<string, any>;
private _changes: Record<string, any> = {};
constructor(data: Record<string, any>) {
this._original = { ...data };
Object.keys(data).forEach((key) => {
this[key] = data[key];
});
}
get dirty(): boolean {
return Object.keys(this._changes).length > 0;
}
get changes(): Record<string, any> {
return { ...this._changes };
}
get attributes(): string[] {
return Object.keys(this._original);
}
getAttribute(key: string) {
return {
get value() {
return this._changes.hasOwnProperty(key) ? this._changes[key] : this._original[key];
},
set value(val: any) {
if (this._original[key] !== val) {
this._changes[key] = val;
} else {
delete this._changes[key];
}
},
get dirty() {
return this._changes.hasOwnProperty(key);
},
reset: () => {
delete this._changes[key];
},
get mixed() {
return false;
}
};
}
setAttribute(key: string, value: any) {
if (this._original[key] !== value) {
this._changes[key] = value;
} else {
delete this._changes[key];
}
}
reset() {
this._changes = {};
}
static fromArray(instances: ChangeSet[]) {
const keys = instances[0].attributes;
const result = new ChangeSet({});
keys.forEach((key) => {
const values = instances.map((instance) => instance[key].value);
const allEqual = values.every((val) => val === values[0]);
result[key] = {
value: allEqual ? values[0] : undefined,
mixed: !allEqual,
};
});
return result;
}
}
class User extends ChangeSet {
@attribute name: string;
@attribute age: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment