Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/durabletask-js/src/task/when-all-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ export class WhenAllTask<T> extends CompositeTask<T[]> {
constructor(tasks: Task<T>[]) {
super(tasks);

this._completedTasks = 0;
this._failedTasks = 0;
// Note: Do NOT re-initialize _completedTasks or _failedTasks here.
// CompositeTask's constructor already initializes them to 0 and then
// processes pre-completed children via onChildCompleted(), which
// increments the counter. Re-initializing would wipe out that count
// and cause the task to hang when some children are already complete.

// An empty task list should complete immediately with an empty result
if (tasks.length === 0) {
Expand Down
129 changes: 129 additions & 0 deletions packages/durabletask-js/test/when-all-task.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { WhenAllTask } from "../src/task/when-all-task";
import { CompletableTask } from "../src/task/completable-task";

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Task import is unused in this test file — only WhenAllTask and CompletableTask are referenced. It should be removed to keep the imports clean.

Copilot uses AI. Check for mistakes.
describe("WhenAllTask", () => {
it("should complete immediately when given an empty task array", () => {
const task = new WhenAllTask<number>([]);

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(false);
expect(task.getResult()).toEqual([]);
});

it("should complete when all pending children complete", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();
const task = new WhenAllTask([child1, child2]);

expect(task.isComplete).toBe(false);

child1.complete(1);
expect(task.isComplete).toBe(false);

child2.complete(2);
expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(false);
expect(task.getResult()).toEqual([1, 2]);
});

it("should fail fast when any child fails", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();
const task = new WhenAllTask([child1, child2]);

child1.fail("child failed");

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(true);
expect(task.getException()).toBeDefined();
});

// Issue #131: WhenAllTask constructor resets _completedTasks counter
it("should complete correctly when constructed with pre-completed children", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();
const child3 = new CompletableTask<number>();

// Complete child1 and child2 before constructing WhenAllTask
child1.complete(10);
child2.complete(20);

const task = new WhenAllTask([child1, child2, child3]);

// 2 of 3 children already complete — task should not be complete yet
expect(task.isComplete).toBe(false);
expect(task.completedTasks).toBe(2);

// Complete the last child
child3.complete(30);

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(false);
expect(task.getResult()).toEqual([10, 20, 30]);
});

it("should complete immediately when all children are pre-completed", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();

child1.complete(1);
child2.complete(2);

const task = new WhenAllTask([child1, child2]);

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(false);
expect(task.completedTasks).toBe(2);
expect(task.getResult()).toEqual([1, 2]);
});

it("should fail immediately when a pre-completed child is failed", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();

child1.fail("pre-failed");

const task = new WhenAllTask([child1, child2]);

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(true);
expect(task.getException()).toBeDefined();
});

it("should not double-complete when child completes after fail-fast", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();
const task = new WhenAllTask([child1, child2]);

child1.fail("first failure");

expect(task.isComplete).toBe(true);
expect(task.isFailed).toBe(true);

// Completing child2 after fail-fast should not change the result
child2.complete(2);
expect(task.isFailed).toBe(true);
expect(task.getException()).toBeDefined();
});

it("should report correct pending tasks count", () => {
const child1 = new CompletableTask<number>();
const child2 = new CompletableTask<number>();
const child3 = new CompletableTask<number>();

child1.complete(1);

const task = new WhenAllTask([child1, child2, child3]);

expect(task.pendingTasks()).toBe(2);

child2.complete(2);
expect(task.pendingTasks()).toBe(1);

child3.complete(3);
expect(task.pendingTasks()).toBe(0);
});
});
Loading