I have an Angular pie chart component built through VegaEmbed (https://github.com/vega/vega-embed) which uses Vega and D3 as underlying graphics dependencies. It renders from supplying a title and some (key, value) pairs. I isolated that component, and modified main.ts to run Jasmine out of Stackblitz to share with you. In this test, I am checking that the pie chart renders indeed SVG <text>
tags for the values "30%" | "70%" and the legend "Combined CEO/Chair" | "Separate CEO/Chair". However, it seems they run too early and VegaEmbed+Vega+D3 are still busy building that SVG. (I inferred what to test by just looking into the DOM through the Chrome dev tools).
https://stackblitz.com/edit/angular-d3-pie-chart-unit-test
I have tried a range of things: async
, FakeAsync
+ tick
, jasmine.clock
, changing the promises logic in my Angular component, etc... fixture.whenStable
gets me a step closer but texts
declared line 50 is still undefined.
I don't know how the internals of Vega, VegaEmbed and D3 are working. If these libraries are not using promises, rather old-fashioned callbacks, then Angular's Zones might fail to wait enough within async
?
What confuses me a little is that console.log(texts);
eventually shows a collection of 4 text SVG element in the console. Yet console.log(texts.length);
displays 0!
expect
statements then ?This is a good question, I have similar issues with Ag-Grid
where I have to wait for the rendering or its callbacks to complete before I do assertions and there is no good way like you mentioned with fakeAsync
, async/done
, etc. At least none that I have found.
A way I have found is to make a utility function like so:
import { interval } from 'rxjs';
.....
export const waitUntil = async (untilTruthy: Function): Promise<boolean> => {
while (!untilTruthy()) {
// older API
// await interval(25).pipe(take(1)).toPromise();
// newer API
await firstValueFrom(interval(25));
}
return Promise.resolve(true);
};
waitUntil
will keep looping every 25ms until the callback function supplied is truthy. The amount of time is up to you.
So in your tests, you can do something like:
it('should render the chart', async () => {
// make your arrangements
// do your action
fixture.detectChanges();
// wait for promises to resolve (optional)
await fixture.whenStable();
await waitUntil(() => /* put a condition here that will resolve to a truthy value
at a later time where the rest of the assertions rely on
it such as the graph being present with its labels*/);
// the rest of your assertions of what should be there what should not
});
You mention setTimeout
working with a value of 0. This works because we are putting what's inside of the setTimeout
at the end of the call stack queue because it runs asynchronously. Doing it this way is still good but I like how the tests read with the waitUntil
approach.