void when_running_an_expensive_action()
    {
        Promise<string> promise = null;
        string eventResult = null;
        Exception eventError = null;
        float eventProgress = 0f;
        bool fulfilledCalled = false, failedCalled = false, progressCalled = false;

        before = () => {
            eventResult = null;
            eventError = null;
            eventProgress = -1f;
            fulfilledCalled = failedCalled = progressCalled = false;
        };

        after = () => promise.Await();

        context["before action finished"] = () => {

            before = () => {
                promise = TestHelper.PromiseWithResult("42", delay);
                promise.OnFulfilled += result => {
                    eventResult = result;
                    fulfilledCalled = true;
                };
                promise.OnFailed += error => {
                    eventError = error;
                    failedCalled = true;
                };
                promise.OnProgressed += progress => {
                    eventProgress = progress;
                    eventProgress = progress;
                };
            };

            it["is unfulfilled"] = () => promise.state.should_be(PromiseState.Unfulfilled);
            it["has progressed 0%"] = () => promise.progress.should_be(0f);
            it["has no result"] = () => promise.result.should_be_null();
            it["has no error"] = () => promise.error.should_be_null();
            it["has a background thread assigned"] = () => {
                promise.thread.should_not_be_null();
                promise.thread.should_not_be(Thread.CurrentThread);
                promise.thread.IsBackground.should_be_true();
            };

            context["await"] = () => {
                it["blocks until finished"] = () => {
                    promise.Await();
                    promise.thread.should_be_null();
                    promise.state.should_be(PromiseState.Fulfilled);
                };

                it["does nothing when promise already finished"] = () => {
                    promise.Await();
                    promise.Await();
                    true.should_be_true();
                };
            };

            context["events"] = () => {
                it["doesn't call OnFulfilled"] = () => fulfilledCalled.should_be_false();
                it["doesn't call OnFail"] = () => failedCalled.should_be_false();
                it["doesn't call OnProgress"] = () => progressCalled.should_be_false();
            };

            context["when action finished"] = () => {

                before = () => promise.Await();

                it["is fulfilled"] = () => promise.state.should_be(PromiseState.Fulfilled);
                it["has progressed 100%"] = () => promise.progress.should_be(1f);
                it["has result"] = () => promise.result.should_be("42");
                it["has no error"] = () => promise.error.should_be_null();
                it["has no thread assigned"] = () => promise.thread.should_be_null();

                context["events"] = () => {
                    it["calls OnFulfilled"] = () => eventResult.should_be("42");
                    it["calls OnFulfilled when adding callback"] = () => {
                        string lateResult = null;
                        promise.OnFulfilled += result => lateResult = result;
                        lateResult.should_be("42");
                    };
                    it["doesn't call OnFailed"] = () => failedCalled.should_be_false();
                    it["doesn't call OnFailed when adding callback"] = () => {
                        var called = false;
                        promise.OnFailed += error => called = true;
                        called.should_be_false();
                    };
                    it["calls OnProgress"] = () => eventProgress.should_be(1f);
                };
            };
        };

        context["when action throws an exception"] = () => {

            before = () => {
                promise = TestHelper.PromiseWithError<string>("error 42", delay);
                promise.OnFulfilled += result => {
                    eventResult = result;
                    fulfilledCalled = true;
                };
                promise.OnFailed += error => {
                    eventError = error;
                    failedCalled = true;
                };
                promise.OnProgressed += progress => {
                    eventProgress = progress;
                    eventProgress = progress;
                };
                promise.Await();
            };

            it["failed"] = () => promise.state.should_be(PromiseState.Failed);
            it["has progressed 0%"] = () => promise.progress.should_be(0f);
            it["has no result"] = () => promise.result.should_be_null();
            it["has error"] = () => promise.error.Message.should_be("error 42");
            it["has no thread assigned"] = () => promise.thread.should_be_null();

            context["events"] = () => {
                it["doesn't call OnFulfilled"] = () => fulfilledCalled.should_be_false();
                it["doesn't call OnFulfilled when adding callback"] = () => {
                    var called = false;
                    promise.OnFulfilled += result => called = true;
                    called.should_be_false();
                };
                it["calls OnFailed"] = () => eventError.Message.should_be("error 42");
                it["calls OnFailed when adding callback"] = () => {
                    Exception lateError = null;
                    promise.OnFailed += error => lateError = error;
                    lateError.Message.should_be("error 42");
                };
                it["doesn't call OnProgress"] = () => progressCalled.should_be_false();
            };
        };

        context["when creating a custom promise with Deferred"] = () => {
            Deferred<string> deferred = null;
            int progressEventCalled = 0;

            before = () => {
                deferred = new Deferred<string>();
                progressEventCalled = 0;
                deferred.OnProgressed += progress => {
                    eventProgress = progress;
                    progressEventCalled++;
                };
            };

            it["progresses"] = () => {
                deferred.Progress(0.3f);
                deferred.Progress(0.6f);
                deferred.Progress(0.9f);
                deferred.Progress(1f);

                eventProgress.should_be(1f);
                deferred.promise.progress.should_be(1f);
                progressEventCalled.should_be(4);
            };

            it["doesn't call OnProgressed when setting equal progress"] = () => {
                deferred.Progress(0.3f);
                deferred.Progress(0.3f);
                deferred.Progress(0.3f);
                deferred.Progress(0.6f);

                eventProgress.should_be(0.6f);
                deferred.promise.progress.should_be(0.6f);
                progressEventCalled.should_be(2);
            };

            it["doesn't call OnProgressed when adding callback when progress is less than 100%"] = () => {
                deferred.Progress(0.3f);
                var called = false;
                deferred.OnProgressed += progress => called = true;
                called.should_be_false();
            };

            it["calls OnProgressed when adding callback when progress is 100%"] = () => {
                deferred.Progress(1f);
                var called = false;
                deferred.OnProgressed += progress => called = true;
                called.should_be_true();
            };
        };

        context["wrap"] = () => {

            Promise<object> wrapped = null;

            context["when wrapped promise fulfills"] = () => {
                before = () => {
                };

                it["forwards fulfill"] = () => {
                    promise = TestHelper.PromiseWithResult("42", delay);
                    wrapped = promise.Wrap<object>();
                    wrapped.Await();
                    wrapped.state.should_be(PromiseState.Fulfilled);
                };

                it["forwards fail"] = () => {
                    promise = TestHelper.PromiseWithError<string>("error 42", delay);
                    wrapped = promise.Wrap<object>();
                    wrapped.Await();
                    wrapped.state.should_be(PromiseState.Failed);
                };

                it["forwards progress"] = () => {
                    var deferred = new Deferred<string>();
                    wrapped = deferred.Wrap<object>();
                    deferred.Progress(0.5f);
                    wrapped.progress.should_be(0.5f);
                };

                it["has initial progress"] = () => {
                    var deferred = new Deferred<string>();
                    deferred.Progress(0.5f);
                    wrapped = deferred.Wrap<object>();
                    wrapped.progress.should_be(0.5f);
                };
            };
        };

        context["toString"] = () => {
            it["returns description of unfulfilled promise"] = () => {
                var deferred = new Deferred<string>();
                deferred.Progress(0.1234567890f);
                deferred.promise.ToString().should_be("[Promise<String>: state = Unfulfilled, progress = 0,123]");
            };

            it["returns description of fulfilled promise"] = () => {
                promise = TestHelper.PromiseWithResult("42");
                promise.Await();
                promise.ToString().should_be("[Promise<String>: state = Fulfilled, result = 42]");
            };

            it["returns description of failed promise"] = () => {
                promise = TestHelper.PromiseWithError<string>("error 42");
                promise.Await();
                promise.ToString().should_be("[Promise<String>: state = Failed, progress = 0, error = error 42]");
            };
        };
    }