-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render array moves with arrows #333
Conversation
┌─> | ||
│ 3, | ||
└── 2, // should be moved | ||
4 // should be undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, it shouldn't say "should be undefined" here.
This looks pretty insane 😀 I'm still concerned about performance, complexity and verbosity. But we can probably put in stop gaps so this will only run in cases where it is manageable. Would it be possible to use unicode arrows to avoid using 3 lines to represent one connection. |
Hmm, those unicode arrows don't seem to compose very well:
|
You are right, you can draw perfect boxes but the arrows does not match 😢 |
Fixed those "should be undefined" bugs.
Yeah, me too :). I tried to address the performance and complexity concerns by consolidating the arrow handling in a single style that's used by both As for the verbosity, it seems like the existing heuristic for falling back to an index-by-index comparison is still doing a good job here. We want to additionally limit the number of moves to render using arrows. In my opinion, none of our existing tests seem to exhibit any bad behavior, though. |
I agree to some extent with @sunesimonsen about verbosity etc, but I am kind of compelled at the sight of this output with the -sinon plugin. Visually it's probably most useful when things are naturally strongly ordered, and when talking about function calls we definitely think of a series of them as having an order. That might explain why I'm less sure about the -dom output with this. Anyway, I'd be tempted to have a way of turning this on/off per diff and it might be worth considering the default. |
Not sure I understand? When comparing arrays, the order is always significant per the semantics?
You mean in the code (when subclassing the |
@papandreou Ah sorry I didn't immediately twig that DOM example was a NodeList. I think I was considering the case where something was declared as an array but the order might matter less, though having said that I can't think of a good example that exists now. Perhaps if we did add support for "these mocks but in any order" to -mitm or equivalent we'd always want a diff without arrows? So yes, I probably meant a within the code opt in/out as appropriate. |
Agreed. In that case the matching semantics are also different, though -- that's why I made http://unexpected.js.org/unexpected-set By the way, I think the arrows will be really nice if we make that change to unexpected-mitm that we discussed where we would initially pick the first request that matches the spec, but if the requests came in the wrong order, we would error out after the test has completed. |
I have a feeling that it would be wise to back out of using this for the following cases:
|
Sure thing. I left those spaces there to honor the |
@papandreou sorry I deleted my comment as I realised that cross arrows would be problematic with my suggestion. But We can always tweak that. |
@papandreou you can still honour the indent, you can just use it as available space. |
But sorry, we should not bikesheet about looks right now :-) |
The current implementation is allocating as much horizontal space as it needs (except that it doesn't try to do any "packing", so getting rid of the indentation doesn't cause any trouble in itself. I removed it in 6a97050 |
hehe 🎉 |
9b2636d
to
e422777
Compare
Rebased on top of the array-changes and array-changes-async fixes (both this branch and the branches in those repos). |
|
I think this is in a good state now. It requires unexpectedjs/array-changes#5 and bruderstein/array-changes-async#5 to be merged and new major versions of array-changes and array-changes-async to be released. |
@papandreou that is good news. I'll take a look at the code soon. |
Thanks -- I'm curious if you have a better idea of how to implement the |
@papandreou If yes that should probably be in magicpen as it is very generic and useful. At some point I was thinking about these kind of things - centering the output of a pen on top of another and other layout stuff. But with a method like this in magicpen, you could probably make the rest as a plugin. |
Yeah, that seemed much easier than rendering all the arrows in one go. |
Yes makes a lot of sense. |
@bruderstein Are you OK with releasing new versions of array-changes(-async) that would potentially break unexpected-htmllike? Should we explore options to move it (and/or the adaptors) to utilize the modules via the array-like type, or would you rather reimplement the arrow rendering? |
Possible future improvements that have come up:
|
Very interesting idea of creating output that follows the array diff more closely. |
It's certainly doable. I'm just not sure that it'd be obvious that expected [ 3, 1, 2 ] to equal [ 1, 2, 3 ]
┌─▷
│ 3,
├── 1, // should be moved
└── 2 // should be moved is equivalent to ┌───▷
│ ┌─▷
│ │ 3,
└─├── 1, // should be moved
└── 2 // should be moved rather than: ┌───▷
│ ┌─▷
│ │ 3,
│ └── 1, // should be moved
└──── 2 // should be moved |
I think you are right. The single arrows are easier to understand. |
@@ -1044,59 +1044,68 @@ module.exports = function (expect) { | |||
if (subjectType.indent) { | |||
output.indentLines(); | |||
} | |||
changes.forEach(function (diffItem, index) { | |||
var packing = utils.packArrows(changes); // NOTE: Will have side effects in changes if the packing results in too many arrow lanes | |||
output.arrowsAlongsideChangeOutputs(packing, changes.map(function (diffItem, index) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It really has nothing to do with the PR, but I think we have crossed the line where we should split up the assertions in separates files (Not in this PR - but after). After we split the assertions into separate files, I think something like this would be much more readable if it was split into some more helper methods.
this.append(valueDiff.amend(delimiterOutput)); | ||
var type = diffItem.type; | ||
if (type === 'moveTarget') { | ||
return output.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like there should be a magicpen construct to map over an array of items and produce output for each item. I know you are just cloning the output, but it feels a bit unsafe that you might append to the outside output by accident. This is just thoughts on how we might refactor later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed!
for (i = 1 ; i < options.width ; i += 1) { | ||
this.text(i === options.width - 1 && options.direction === 'down' ? '▷' : '─', styles); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably make a magicpen-arrow plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can make it a bit more generic, and if unexpected-htmllike/-react cannot reach this feature through the array-like type, I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it might also mean implementing a lot of features that we wont use.
} | ||
} while (contentLeft); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we talked about, this style should move to magicpen core when it has proven it's stability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
this.block(function () { | ||
this.merge(arrows); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If would be better if this was just:
this.block(function () {
this.merge(arrows);
});
Magicpen should be smart enough to avoid the extra block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and merge should be smart enough to handle one pen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way merge
is implemented, it takes an array of pens and produces a fresh pen with the merged output. It does handle the single pen case, but I'd like to avoid creating a copy here. My initial attempt at implementing the merging was to merge one or more other pens into the current pen, but that turned out to be harder to implement and not as understandable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial attempt at implementing the merging was to merge one or more other pens into the current pen, but that turned out to be harder to implement and not as understandable.
Ohh I though it worked that way. That is probably how I want it to work in magicpen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I agree that would be the most useful API-wise.
} | ||
}); | ||
} | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method feels a lot like Déjà vu, it would be nice of the two method shared more common parts. But let's look at if it is possible to refactored it afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, a long time ago we made the decision to keep the array/object diffs separate from the generation of the 'to satisfy' diffs. Since then there has been a lot of fluctuation back and forth, but I think they're still sufficiently different that it could pose a challenge to reconsolidate them.
If we decide to go for #330 they would align closer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not saying that we should reconcile them, just that we should look for common parts that we can extract :-)
"array-changes": "1.3.1", | ||
"array-changes-async": "2.2.1", | ||
"array-changes": "unexpectedjs/array-changes#e2385e5b3130e7c715dee6d77489c78565833bcb", | ||
"array-changes-async": "bruderstein/array-changes-async#8c965f125be6f45604ef764a7750b4c23eafd9f9", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We of cause needs these released before this can be merged. But I guess we are waiting on an answer from @bruderstein - right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'd like him to sign off on it so I don't create any stumbling blocks for unexpected-htmllike and unexpected-react.
for (i = 1 ; i < options.width ; i += 1) { | ||
this.text(i === options.width - 1 && options.direction === 'down' ? '▷' : '─', styles); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably make a magicpen-arrow plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be ready to merge after the dependencies has been released. But we should really take unexpected for a refactoring dance afterwards.
I'm still a bit curious about the performance effects, can we get any kind of measurements, comparing two arrays with 500 items. We should probably skip the diff for arrays with more then 50 items - I don't really know what the limit should be.
Are we completely sure the code does the right thing? Should we create a unexpected-check test that compares random arrays, when it fails checks that the output contains the same number of moves, removes and inserts as the array-changes?
@@ -0,0 +1,24 @@ | |||
/*global expect*/ | |||
describe('overlay', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be merge style
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Thanks for reviewing!
I tried writing a quick script: var a1 = new Array(500);
var a2 = new Array(500);
for (var i = 0 ; i < 500 ; i += 1) {
a2[i] = i;
if (i * 2 < 500) {
a1[i * 2] = i;
} else {
a1[(i - 250) * 2 + 1] = i;
}
}
var expect = require('./');
for (var i = 0 ; i < 10 ; i += 1) {
try {
expect(a1, 'to equal', a2);
} catch (e) {}
}
expect(a1, 'to equal', a2); To my surprise it actually runs faster with these changes than on master, 4.3 seconds vs. 5.1 seconds. The
I'm reasonably sure it does the right thing, but it's an interesting idea to exercise it like that. |
Creating an unexpected-check test might be a bit more challenging than I initially imagined as you need to take the fallback to indexed diffing into account. But you could make a test that asserts that the either don't contain an arrow or that the diff is consistent with the array-changes. But I'm completely okay with merging this without such a test. |
The interval packer already has randomized tests so that part should be good: https://github.com/One-com/greedy-interval-packer/blob/master/test/randomized.spec.js |
…ce for the arrows.
…nserts and moveSources to be ordered differently (harmless).
b8fb815
to
917723b
Compare
Fixed up and rebased. Merging if CI is green. |
Affects the array-like diff and the
<array-like> to [exhaustively] satisfy <array-like>
assertion.Here's what it'll look like in unexpected-sinon: https://github.com/unexpectedjs/unexpected-sinon/compare/feature/arrayMoves
And unexpected-dom: papandreou/unexpected-dom@11b7199 (only one of the existing tests seemed to contain a move)