javascript - react

This commit is contained in:
James Walker 2018-10-03 13:00:19 -04:00
parent 238b93fec3
commit 2362c2cf15
Signed by: walkah
GPG Key ID: 3C127179D6086E93
5 changed files with 7513 additions and 0 deletions

View File

@ -0,0 +1,48 @@
# React
Implement a basic reactive system.
Reactive programming is a programming paradigm that focuses on how values
are computed in terms of each other to allow a change to one value to
automatically propagate to other values, like in a spreadsheet.
Implement a basic reactive system with cells with settable values ("input"
cells) and cells with values computed in terms of other cells ("compute"
cells). Implement updates so that when an input value is changed, values
propagate to reach a new stable system state.
In addition, compute cells should allow for registering change notification
callbacks. Call a cells callbacks when the cells value in a new stable
state has changed from the previous stable state.
## Setup
Go through the setup instructions for Javascript to
install the necessary dependencies:
[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation)
## Requirements
Install assignment dependencies:
```bash
$ npm install
```
## Making the test suite pass
Execute the tests with:
```bash
$ npm test
```
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by
changing `xtest` to `test`.
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

7080
javascript/react/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
{
"name": "exercism-javascript",
"version": "0.0.0",
"description": "Exercism exercises in Javascript.",
"author": "Katrina Owen",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/javascript"
},
"devDependencies": {
"babel-jest": "^21.2.0",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-preset-env": "^1.4.0",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^5.0.1",
"eslint-plugin-react": "^7.0.1",
"jest": "^21.2.1"
},
"jest": {
"modulePathIgnorePatterns": [
"package.json"
]
},
"babel": {
"presets": [
[
"env",
{
"targets": [
{
"node": "current"
}
]
}
]
],
"plugins": [
[
"babel-plugin-transform-builtin-extend",
{
"globals": [
"Error"
]
}
],
[
"transform-regenerator"
]
]
},
"scripts": {
"test": "jest --no-cache ./*",
"watch": "jest --no-cache --watch ./*",
"lint": "eslint .",
"lint-test": "eslint . && jest --no-cache ./* "
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"es6": true,
"node": true,
"jest": true
},
"extends": "airbnb",
"rules": {
"import/no-unresolved": "off",
"import/extensions": "off"
}
},
"licenses": [
"MIT"
],
"dependencies": {}
}

94
javascript/react/react.js vendored Normal file
View File

@ -0,0 +1,94 @@
export class InputCell {
constructor(value) {
this.value = value;
this.computeCells = [];
}
setValue(value) {
this.value = value;
this.update();
this.runCallbacks();
}
addCompute(cell) {
this.computeCells.push(cell);
}
update() {
this.computeCells.forEach((cell) => {
cell.update();
});
}
runCallbacks() {
this.computeCells.forEach((cell) => {
cell.runCallbacks();
})
}
}
export class ComputeCell {
constructor(inputCells, fn) {
this.fn = fn;
this.valueChanged = false;
this.computeCells = [];
this.callbacks = [];
this.addInputs(inputCells);
this.update();
}
addCompute(cell) {
this.computeCells.push(cell);
}
addCallback(callback) {
callback.lastRunValue = callback.fn(this);
this.callbacks.push(callback);
}
removeCallback(callback) {
const i = this.callbacks.indexOf(callback);
if (i >= 0) {
this.callbacks.splice(i, 1);
}
}
runCallbacks() {
this.computeCells.forEach((cell) => {
cell.runCallbacks();
});
this.callbacks.forEach((callback, i) => {
if (callback.lastRunValue !== this.value) {
callback.update(this);
callback.lastRunValue = this.value;
}
});
}
addInputs(inputCells) {
this.inputCells = inputCells;
inputCells.forEach((cell) => {
cell.addCompute(this);
});
}
update() {
this.value = this.fn(this.inputCells);
this.computeCells.forEach((cell) => {
cell.update();
});
}
}
export class CallbackCell {
constructor(fn) {
this.fn = fn;
this.values = [];
}
update(cell) {
this.values.push(this.fn(cell));
}
}

View File

@ -0,0 +1,211 @@
import {InputCell, ComputeCell, CallbackCell} from './react';
describe('React module', () => {
test('accepts input', () => {
const inputCell = new InputCell(10);
expect(inputCell.value).toEqual(10);
});
test('allows input cell value to be set', () => {
const inputCell = new InputCell(4);
inputCell.setValue(20);
expect(inputCell.value).toEqual(20);
});
test('allows setting compute cells', () => {
const inputCell = new InputCell(1);
const fn = inputCells => inputCells[0].value + 1;
const computeCell = new ComputeCell([inputCell], fn);
expect(computeCell.value).toEqual(2);
});
test('compute cell takes inputs in correct order', () => {
const inputCells = [
new InputCell(1),
new InputCell(2)
];
const computeCell = new ComputeCell(
inputCells,
inputs => inputs[0].value + inputs[1].value * 10
);
expect(computeCell.value).toEqual(21);
});
test('compute cells update value when inputs are changed', () => {
const inputCell = new InputCell(1);
const computeCell = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
inputCell.setValue(3);
expect(computeCell.value).toEqual(4);
});
test('compute cells can depend on other compute cells', () => {
const inputCell = new InputCell(1);
const timesTwo = new ComputeCell(
[inputCell],
inputs => inputs[0].value * 2
);
const timesThirty = new ComputeCell(
[inputCell],
inputs => inputs[0].value * 30
);
const sum = new ComputeCell(
[timesTwo, timesThirty],
inputs => inputs[0].value + inputs[1].value
);
expect(sum.value).toEqual(32);
inputCell.setValue(3);
expect(sum.value).toEqual(96);
});
test('compute cells fire callbacks', () => {
const inputCell = new InputCell(1);
const output = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
const callback = new CallbackCell(cell => cell.value);
output.addCallback(callback);
inputCell.setValue(3);
expect(callback.values).toEqual([4]);
});
test('callbacks fire only when output values change', () => {
const inputCell = new InputCell(1);
const output = new ComputeCell(
[inputCell],
inputs => inputs[0].value < 3 ? 111 : 222
);
const callback = new CallbackCell(cell => cell.value);
output.addCallback(callback);
inputCell.setValue(2);
expect(callback.values).toEqual([]);
inputCell.setValue(4);
expect(callback.values).toEqual([222]);
});
test('callbacks can be added and removed', () => {
const inputCell = new InputCell(1);
const output = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
const callback1 = new CallbackCell(cell => cell.value);
const callback2 = new CallbackCell(cell => cell.value);
output.addCallback(callback1);
output.addCallback(callback2);
inputCell.setValue(31);
output.removeCallback(callback1);
const callback3 = new CallbackCell(cell => cell.value);
output.addCallback(callback3);
inputCell.setValue(41);
expect(callback1.values).toEqual([32]);
expect(callback2.values).toEqual([32, 42]);
expect(callback3.values).toEqual([42]);
});
test('removing a callback multiple times doesn\'t interfere with other callbacks', () => {
const inputCell = new InputCell(1);
const output = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
const callback1 = new CallbackCell(cell => cell.value);
const callback2 = new CallbackCell(cell => cell.value);
output.addCallback(callback1);
output.addCallback(callback2);
output.removeCallback(callback1);
output.removeCallback(callback1);
output.removeCallback(callback1);
inputCell.setValue(2);
expect(callback1.values).toEqual([]);
expect(callback2.values).toEqual([3]);
});
test('callbacks should only be called once, even if multiple dependencies change', () => {
const inputCell = new InputCell(1);
const plusOne = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
const minusOne1 = new ComputeCell(
[inputCell],
inputs => inputs[0].value - 1
);
const minusOne2 = new ComputeCell(
[minusOne1],
inputs => inputs[0].value - 1
);
const output = new ComputeCell(
[plusOne, minusOne2],
inputs => inputs[0].value * inputs[1].value
);
const callback1 = new CallbackCell(cell => cell.value);
output.addCallback(callback1);
inputCell.setValue(4);
expect(callback1.values).toEqual([10]);
});
test('callbacks should not be called if dependencies change but output value doesn\'t change', () => {
const inputCell = new InputCell(1);
const plusOne = new ComputeCell(
[inputCell],
inputs => inputs[0].value + 1
);
const minusOne = new ComputeCell(
[inputCell],
inputs => inputs[0].value - 1
);
const alwaysTwo = new ComputeCell(
[plusOne, minusOne],
inputs => inputs[0].value - inputs[1].value
);
const callback = new CallbackCell(cell => cell.value);
alwaysTwo.addCallback(callback);
inputCell.setValue(2);
inputCell.setValue(3);
inputCell.setValue(4);
inputCell.setValue(5);
expect(callback.values).toEqual([]);
});
});