Skip to content

Commit

Permalink
Finalized tutorial 8
Browse files Browse the repository at this point in the history
  • Loading branch information
untio11 committed Nov 23, 2023
1 parent 03b5aa0 commit 748978d
Showing 1 changed file with 98 additions and 56 deletions.
154 changes: 98 additions & 56 deletions tutorial/8 - advanced.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { atom, constant, Derivable, derive, SettableDerivable } from '@skunkteam
import { Map as ImmutableMap } from 'immutable';

/**
* **Your Turn**
* ** Your Turn **
*
* If you see this variable, you should do something about it. :-)
*/
export const __YOUR_TURN__ = {} as any;
Expand All @@ -23,7 +24,8 @@ describe.skip('advanced', () => {
const c = constant('value') as unknown as SettableDerivable<string>;

/**
* **Your Turn**
* ** Your Turn **
*
* What do you expect this `Derivable` to do on `.set()`, `.get()` etc?
*/

Expand All @@ -35,21 +37,24 @@ describe.skip('advanced', () => {
});

/**
* Collections in `ImmutableJS` are immutable, so any modification to a collection will create a new one.
* This results in every change needing a `.get()` and a `.set()` on a `Derivable`.
* Collections in `ImmutableJS` are immutable, so any modification to a
* collection will create a new one. This results in every change needing a
* `.get()` and a `.set()` on a `Derivable`.
*
* To make this pattern a little bit easier, the `.swap()` method can be used.
* The given function will get the current value of the `Derivable` and any return value will be set as the new value.
* To make this pattern a little bit easier, the `.swap()` method can be
* used. The given function will get the current value of the `Derivable`
* and any return value will be set as the new value.
*/
it('`.swap()`', () => {
// This is a separate function, because you might be able to use this later
// This is a separate function because you might want to use this later.
function plusOne(num: number) {
return num + 1;
}

const myCounter$ = atom(0);
/**
* **Your Turn**
* ** Your Turn **
*
* Rewrite the `.get()`/`.set()` combos below using `.swap()`.
*/
expect(false).toBe(true);
Expand All @@ -62,7 +67,8 @@ describe.skip('advanced', () => {
});

/**
* As an alternative to `.get()` and `.set()`, there is also the `.value` accessor.
* As an alternative to `.get()` and `.set()`, there is also the `.value`
* accessor.
*/
describe('.value', () => {
/**
Expand All @@ -73,13 +79,15 @@ describe.skip('advanced', () => {
const myAtom$ = atom('foo');

/**
* **Your Turn**
* ** Your Turn **
*
* Use the `.value` accessor to get the current value.
*/
expect(__YOUR_TURN__).toEqual('foo');

/**
* **Your Turn**
* ** Your Turn **
*
* Now use the `.value` accessor to set a 'new value'.
*/
myAtom$.value = __YOUR_TURN__;
Expand All @@ -95,14 +103,15 @@ describe.skip('advanced', () => {
const myAtom$ = atom.unresolved<string>();

/**
* **Your Turn**
* ** Your Turn **
*/
expect(myAtom$.value).toEqual(__YOUR_TURN__);
});

/**
* As a result, if `.value` is used inside a derivation, it will also replace `unresolved` with `undefined`.
* So `unresolved` will not automatically propagate when using `.value`.
* As a result, if `.value` is used inside a derivation, it will also
* replace `unresolved` with `undefined`. So `unresolved` will not
* automatically propagate when using `.value`.
*/
it('will stop propagation of `unresolved` in `.derive()`', () => {
const myAtom$ = atom('foo');
Expand All @@ -114,7 +123,8 @@ describe.skip('advanced', () => {
expect(usingVal$.get()).toEqual('foo');

/**
* **Your Turn**
* ** Your Turn **
*
* We just created two `Derivable`s that are almost exactly the same.
* But what happens when their source becomes `unresolved`?
*/
Expand All @@ -141,7 +151,8 @@ describe.skip('advanced', () => {
it('triggers when the source changes', () => {
const myAtom$ = atom(1);
/**
* **Your Turn**
* ** Your Turn **
*
* Use the `.map()` method to create the expected output below
*/
const mappedAtom$: Derivable<string> = __YOUR_TURN__;
Expand Down Expand Up @@ -170,7 +181,8 @@ describe.skip('advanced', () => {

myRepeat$.value = 3;
/**
* **Your Turn**
* ** Your Turn **
*
* We changed`myRepeat$` to equal 3.
* Do you expect both reactors to have fired? And with what?
*/
Expand All @@ -182,7 +194,8 @@ describe.skip('advanced', () => {

myString$.value = 'ha';
/**
* **Your Turn**
* ** Your Turn **
*
* And now that we have changed `myString$`? And when `myRepeat$` changed again?
*/
expect(deriveReactSpy).toHaveBeenCalledTimes(__YOUR_TURN__);
Expand All @@ -199,51 +212,60 @@ describe.skip('advanced', () => {
expect(mapReactSpy).toHaveBeenLastCalledWith(__YOUR_TURN__, expect.toBeFunction());

/**
* As you can see, a change in `myString$` will not trigger an update.
* But if an update is triggered, `myString$` will be called and the new value will be used.
* As you can see, a change in `myString$` will not trigger an
* update. But if an update is triggered, `myString$` will be called
* and the new value will be used.
*/
});

/**
* Since `.map()` is a relatively simple mapping of input value to output value.
* It can often be reversed. In that case you can use that reverse mapping to create a `SettableDerivable`.
* Since `.map()` is a relatively simple mapping of input value to
* output value. It can often be reversed. In that case you can use that
* reverse mapping to create a `SettableDerivable`.
*/
it('can be settable', () => {
const myAtom$ = atom(1);

/**
* **Your Turn**
* ** Your Turn **
*
* Check the comments and `expect`s below to see what should be
* implemented exactly.
*/
const myInverse$ = myAtom$.map(
// This first function is called when getting
// This first function is called when getting...
n => -n,
// The second is called when setting, you may want to fix this one though
// ...and this second function is called when setting.
__YOUR_TURN__,
);

// The original `atom` was set to 1, so we want the inverse to
// be equal -1.
expect(myInverse$.get()).toEqual(-1);

// Now we set the inverse to -2 directly, so we expect the original
// `atom` to be equal to 2.
myInverse$.set(-2);

/**
* **Your Turn**
*/
expect(myAtom$.get()).toEqual(__YOUR_TURN__);
expect(myInverse$.get()).toEqual(__YOUR_TURN__);
expect(myAtom$.get()).toEqual(2);
expect(myInverse$.get()).toEqual(-2);
});
});

/**
* `.pluck()` is a special case of the `.map()` method.
* If a collection of values, like an Object, Map, Array is the result of a `Derivable` one of those values can be plucked into a new `Derivable`.
* If a collection of values, like an Object, Map, Array is the result of a
* `Derivable` one of those values can be plucked into a new `Derivable`.
* This plucked `Derivable` can be settable, if the source supports it.
*
* The way properties are plucked is pluggable, but by default both `<source>.get(<prop>)` and `<source>[<prop>]` are supported.
* To support basic Objects, Maps and Arrays.
* The way properties are plucked is pluggable, but by default both
* `<source>.get(<prop>)` and `<source>[<prop>]` are supported to support
* basic Objects, Maps and Arrays.
*
* *Note that normally when a value of a collection changes, the reference
* does not. This means that setting a plucked property of a regular
* Object/Array/Map will not cause any reaction on that source `Derivable`.
*
* *Note that normally when a value of a collection changes, the reference does not.*
* *This means that setting a plucked property of a regular Object/Array/Map will not cause any reaction on that source `Derivable`.*
* *ImmutableJS can help fix this problem*
* ImmutableJS can help fix this problem*
*/
describe('`.pluck()`', () => {
const reactSpy = jest.fn();
Expand All @@ -262,78 +284,98 @@ describe.skip('advanced', () => {
}),
);
/**
* **Your Turn**
* ** Your Turn **
*
* `.pluck()` 'firstProp' from `myMap$`.
*
* * Hint: you'll have to cast the result from `.pluck()`.
*/
firstProp$ = __YOUR_TURN__;
});

/**
* Once a property is plucked in a new `Derivable`. This `Derivable` can be used as a regular `Derivable`.
* Once a property is plucked in a new `Derivable`. This `Derivable` can
* be used as a regular `Derivable`.
*/
it('can be used as a normal `Derivable`', () => {
firstProp$.react(reactPropSpy, { skipFirst: true });

/**
* **Your Turn**
* What do you expect the plucked `Derivable` to look like? And what happens when we `.set()` it?
* ** Your Turn **
*
* What do you expect the plucked `Derivable` to look like? And what
* happens when we `.set()` it?
*/
expect(firstProp$.get()).toEqual(__YOUR_TURN__);

firstProp$.set('other value'); // the plucked `Derivable` should be settable
expect(firstProp$.get()).toEqual(__YOUR_TURN__); // is the `Derivable` value the same as was set?
// the plucked `Derivable` should be settable
firstProp$.set('other value');
// is the `Derivable` value the same as was set?
expect(firstProp$.get()).toEqual(__YOUR_TURN__);

// How many times was the spy called? Note the `skipFirst`..
expect(reactPropSpy).toHaveBeenCalledTimes(__YOUR_TURN__);

// ... and what was the value?
// ...and what was the value?
expect(reactPropSpy).toHaveBeenLastCalledWith(__YOUR_TURN__, expect.toBeFunction());
});

/**
* If the source of the plucked `Derivable` changes, the plucked `Derivable` will change as well.
* As long as the change affects the plucked property of course.
* If the source of the plucked `Derivable` changes, the plucked
* `Derivable` will change as well. As long as the change affects the
* plucked property of course.
*/
it('will react to changes in the source `Derivable`', () => {
firstProp$.react(reactPropSpy, { skipFirst: true });

/**
* **Your Turn**
* ** Your Turn **
*
* We will set `secondProp`, will this affect `firstProp$`?
*
* *Note: this `map` refers to `ImmutableMap`, not to the
* `Derivable.map()` we saw earlier in the tutorial.*
*/
myMap$.swap(map => map.set('secondProp', 'new value'));
// How many times was the spy called? Note the `skipFirst`..
expect(reactPropSpy).toHaveBeenCalledTimes(__YOUR_TURN__);

// ... and what was the value?
expect(reactPropSpy).toHaveBeenLastCalledWith(__YOUR_TURN__, expect.toBeFunction());
// How many times was the spy called? Note the `skipFirst`.
expect(reactPropSpy).toHaveBeenCalledTimes(__YOUR_TURN__);

/**
* **Your Turn**
* ** Your Turn **
*
* And what if we set `firstProp`?
*/
myMap$.swap(map => map.set('firstProp', 'new value'));

// How many times was the spy called? Note the `skipFirst`..
expect(reactPropSpy).toHaveBeenCalledTimes(__YOUR_TURN__);

// ... and what was the value?
// ...and what was the value?
expect(reactPropSpy).toHaveBeenLastCalledWith(__YOUR_TURN__, expect.toBeFunction());
});

/**
* Before, we saw how a change in the source of the plucked `Derivable`
* propagates to it. Now the question is: does this go the other way
* too?
*
* We saw that we can `.set()` the value of the plucked `Derivable`, so
* what happens to the source if we do that?
*/
it('will write through to the source `Derivable`', () => {
myMap$.react(reactSpy, { skipFirst: true });

/**
* **Your Turn**
* So what if we set `firstProp$`? Does this propagate to the source `Derivable`?
* ** Your Turn **
*
* So what if we set `firstProp$`? Does this propagate to the source
* `Derivable`?
*/
firstProp$.set(__YOUR_TURN__);
expect(reactSpy).toHaveBeenCalledTimes(__YOUR_TURN__);
expect(myMap$.get()).toEqual(__YOUR_TURN__);
expect(myMap$.get().get('firstProp')).toEqual(__YOUR_TURN__);
expect(myMap$.get().get('secondProp')).toEqual(__YOUR_TURN__);
});
});
});

0 comments on commit 748978d

Please sign in to comment.