Reactive pos-label and pos-description#197
Reactive pos-label and pos-description#197jg10-mastodon-social wants to merge 22 commits intomainfrom
Conversation
| ), | ||
| startWith(this.anyValue(...predicateUris)), | ||
| ); | ||
| } |
There was a problem hiding this comment.
This is a bit different to other observe functions because I've tried to use the stream of changes directly and avoid unnecessary calls to anyValue.
I should perhaps have implemented this through multiple commits to improve clarity, but I did attempt to follow TDD so the tests should correspond to features of this function.
There was a problem hiding this comment.
question: Why is it important that anyValue is not called? Is it that expensive?
There was a problem hiding this comment.
I've had performance issues in my experiments generally but I don't yet know whether anyValue is a problem specifically.
rdflib.js hopefully uses indexes, but whatever it does internally to search through the store to lookup the subject and predicate would be much more expensive than just reacting to the streamed value.
So this might be premature optimisation and we could call anyValue here too - though it would also be useful to give a precedent of what can be done with the rxjs architecture. If rxjs ends up being too confusing maybe we should be looking at other reactive architectures for the future.
| const observable = thing.observeLabel(); | ||
| observable.subscribe(subscriber); | ||
|
|
||
| expect(subscriber.mock.lastCall).toEqual([thing.label()]); |
There was a problem hiding this comment.
While observeLabel doesn't call label at all, it does call labelFromUri so I thought this might be a sufficient test without duplicating all of "if nothing is found in the store".
From a TDD perspective, I wrote this as a failing test before implementing
.pipe(map((value) => value ?? labelFromUri(this.uri)));There was a problem hiding this comment.
I think this is reasonable
25ffd0b to
e85a6f2
Compare
|
I'm currently planning to implement the e2e test only when the dynamic pane feature is live. This update can be manually explored by adding a label or description literal in a generic thing page when there isn't one yet. The label and description in the overview then update live. |
angelo-v
left a comment
There was a problem hiding this comment.
I am not fully through with the review, but I left some comments you can alreay work on and perhaps answer some of my questions. I try to come back to it asap (maybe monday)
Overall this is good work and a valuable addition, even if some rework is still needed
| */ | ||
| observeLabel() { | ||
| return this.observeAnyValue( | ||
| "http://www.w3.org/2006/vcard/ns#fn", |
There was a problem hiding this comment.
suggestion: do not duplicate the label predicates, but extract them to a constant that is used both by label() and observeLabel
|
|
||
| const currentValue = this.anyValue(...predicateUris); | ||
| const dirty$ = | ||
| typeof currentValue === "undefined" |
There was a problem hiding this comment.
question: Why not just currentValue === undefined?
There was a problem hiding this comment.
That is indeed simpler! I somehow learnt to use typeof 😅
| */ | ||
| observeDescription() { | ||
| return this.observeAnyValue( | ||
| "http://purl.org/dc/terms/description", |
There was a problem hiding this comment.
suggestion: same as for label
|
|
||
| receiveResource = (resource: Thing) => { | ||
| this.resource = resource; | ||
| resource |
There was a problem hiding this comment.
issue: I think we should cancel the old subscription when receiving a new resource. You did so in pos-switch. Is there a reason you are not doing it here, or did you just forget?
There was a problem hiding this comment.
I think I was just working on them in parallel and hadn't yet thought to apply that pattern here.
| const observable = thing.observeLabel(); | ||
| observable.subscribe(subscriber); | ||
|
|
||
| expect(subscriber.mock.lastCall).toEqual([thing.label()]); |
There was a problem hiding this comment.
I think this is reasonable
| expect(subscriber).toHaveBeenCalledTimes(2); | ||
| }); | ||
|
|
||
| it("pushes a value if none was present, without calling anyValue again", () => { |
There was a problem hiding this comment.
suggestion: This test does not use the setup from before each. Split the tests that need the setup and those who dont into separate nested describe blocks
| ), | ||
| startWith(this.anyValue(...predicateUris)), | ||
| ); | ||
| } |
There was a problem hiding this comment.
question: Why is it important that anyValue is not called? Is it that expensive?
| return merge(dirty$, addition$).pipe( | ||
| debounceTime(250), | ||
| map((value) => | ||
| typeof value === "string" ? value : this.anyValue(...predicateUris), |
There was a problem hiding this comment.
typeof value === "string"
issue: this check really confused me. It is basically a dirty way to check whether the observed value comes from addtions
There was a problem hiding this comment.
thought: I am still having trouble to fully understand the function and the role of the dirty$ observable. Perhaps you can explain a bit. I need to finish my review for now, but will come back to this. If you find the time to explain a bit in the meantime this is much appreciated. My gut feeling is that the whole function could and should be simplified.
There was a problem hiding this comment.
Yes, that's a switch between calling anyValue and using the value from additions
I'm still getting my head around rxjs and its functionality but the intention is that addition is only read when the current value of anyValue is dirty, i.e. it hasn't been set yet or there has been a removal.
We could also simplify and just trigger for every new addition (there's already a debounce to handle concurrent removals). This would cause the label to change, i.e. a different anyValue would be selected just because there's more than one defined.
The current behaviour is intended to guarantee that once anyValue has been found, additional values in the data will be ignored.
| }); | ||
| }); | ||
|
|
||
| describe("observeAnyValue", () => { |
There was a problem hiding this comment.
thought: I think there are not enough tests given the complexity of the function. What about having multiple different (matching) predicates that "come and go" over time. How does the value change or stay consistent?
There was a problem hiding this comment.
I did try to cover a lot of behaviours but I admit I didn't try to lay out all possible combinations of possible input states.
The existing tests specifically describe the behaviour I was trying to achieve, which could perhaps be simplified at the expense of some user surprise and/or marginal but unknown performance impact.
Co-authored-by: Angelo Veltens <angelo+github.com@veltens.org>
| }); | ||
|
|
||
| // TODO make this green | ||
| it.skip("emits new value after add-remove-add cycle", () => { |
There was a problem hiding this comment.
@jg10-mastodon-social this actually highlights a bug and supports my gut feeling that test coverage is not high enough
Closes #175
observeAnyValue