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