javascript - react
This commit is contained in:
parent
238b93fec3
commit
2362c2cf15
48
javascript/react/README.md
Normal file
48
javascript/react/README.md
Normal 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 cell’s callbacks when the cell’s 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
7080
javascript/react/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
javascript/react/package.json
Normal file
80
javascript/react/package.json
Normal 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
94
javascript/react/react.js
vendored
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
211
javascript/react/react.spec.js
Normal file
211
javascript/react/react.spec.js
Normal 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([]);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user