/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import {ifEnvSupports, ifEnvSupportsWithDone, isFirefox, isSafari} from '../test-util';

declare const global: any;

describe(
    'fetch', ifEnvSupports('fetch', function() {
      let testZone: Zone;
      beforeEach(() => {
        testZone = Zone.current.fork({name: 'TestZone'});
      });
      it('should work for text response', function(done) {
        testZone.run(function() {
          global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
              .then(function(response: any) {
                const fetchZone = Zone.current;
                expect(fetchZone.name).toBe(testZone.name);

                response.text().then(function(text: string) {
                  expect(Zone.current.name).toBe(fetchZone.name);
                  expect(text.trim()).toEqual('{"hello": "world"}');
                  done();
                });
              });
        });
      });

      it('should work for json response', function(done) {
        testZone.run(function() {
          global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
              .then(function(response: any) {
                const fetchZone = Zone.current;
                expect(fetchZone.name).toBe(testZone.name);

                response.json().then(function(obj: any) {
                  expect(Zone.current.name).toBe(fetchZone.name);
                  expect(obj.hello).toEqual('world');
                  done();
                });
              });
        });
      });

      it('should work for blob response', function(done) {
        testZone.run(function() {
          global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
              .then(function(response: any) {
                const fetchZone = Zone.current;
                expect(fetchZone.name).toBe(testZone.name);

                // Android 4.3- doesn't support response.blob()
                if (response.blob) {
                  response.blob().then(function(blob: any) {
                    expect(Zone.current.name).toBe(fetchZone.name);
                    expect(blob instanceof Blob).toEqual(true);
                    done();
                  });
                } else {
                  done();
                }
              });
        });
      });

      it('should work for arrayBuffer response', function(done) {
        testZone.run(function() {
          global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
              .then(function(response: any) {
                const fetchZone = Zone.current;
                expect(fetchZone.name).toBe(testZone.name);

                // Android 4.3- doesn't support response.arrayBuffer()
                if (response.arrayBuffer) {
                  response.arrayBuffer().then(function(blob: any) {
                    expect(Zone.current).toBe(fetchZone);
                    expect(blob instanceof ArrayBuffer).toEqual(true);
                    done();
                  });
                } else {
                  done();
                }
              });
        });
      });

      it('should throw error when send crendential',
         ifEnvSupportsWithDone(isFirefox, function(done: DoneFn) {
           testZone.run(function() {
             global['fetch']('http://user:password@example.com')
                 .then(
                     function(response: any) {
                       fail('should not success');
                     },
                     (error: any) => {
                       expect(Zone.current.name).toEqual(testZone.name);
                       expect(error.constructor.name).toEqual('TypeError');
                       done();
                     });
           });
         }));

      describe('macroTask', () => {
        const logs: string[] = [];
        let fetchZone: Zone;
        let fetchTask: any = null;
        beforeEach(() => {
          logs.splice(0);
          fetchZone = Zone.current.fork({
            name: 'fetch',
            onScheduleTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task) => {
              if (task.type !== 'eventTask') {
                logs.push(`scheduleTask:${task.source}:${task.type}`);
              }
              if (task.source === 'fetch') {
                fetchTask = task;
              }
              return delegate.scheduleTask(target, task);
            },
            onInvokeTask:
                (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task, applyThis: any,
                 applyArgs: any) => {
                  if (task.type !== 'eventTask') {
                    logs.push(`invokeTask:${task.source}:${task.type}`);
                  }
                  return delegate.invokeTask(target, task, applyThis, applyArgs);
                },
            onCancelTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task) => {
              if (task.type !== 'eventTask') {
                logs.push(`cancelTask:${task.source}:${task.type}`);
              }
              return delegate.cancelTask(target, task);
            }
          });
        });
        it('fetch should be considered as macroTask', (done: DoneFn) => {
          fetchZone.run(() => {
            global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
                .then(function(response: any) {
                  expect(Zone.current.name).toBe(fetchZone.name);
                  expect(logs).toEqual([
                    'scheduleTask:fetch:macroTask', 'scheduleTask:Promise.then:microTask',
                    'invokeTask:Promise.then:microTask', 'invokeTask:fetch:macroTask',
                    'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
                  ]);
                  done();
                });
          });
        });

        it('cancel fetch should invoke onCancelTask',
           ifEnvSupportsWithDone('AbortController', (done: DoneFn) => {
             if (isSafari()) {
               // safari not work with AbortController
               done();
               return;
             }
             fetchZone.run(() => {
               const AbortController = global['AbortController'];
               const abort = new AbortController();
               const signal = abort.signal;
               global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json', {signal})
                   .then(function(response: any) {
                     fail('should not get response');
                   })
                   .catch(function(error: any) {
                     expect(error.name).toEqual('AbortError');
                     expect(logs).toEqual([
                       'scheduleTask:fetch:macroTask', 'cancelTask:fetch:macroTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
                     ]);
                     done();
                   });
               abort.abort();
             });
           }));

        it('cancel fetchTask should trigger abort',
           ifEnvSupportsWithDone('AbortController', (done: DoneFn) => {
             if (isSafari()) {
               // safari not work with AbortController
               done();
               return;
             }
             fetchZone.run(() => {
               const AbortController = global['AbortController'];
               const abort = new AbortController();
               const signal = abort.signal;
               global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json', {signal})
                   .then(function(response: any) {
                     fail('should not get response');
                   })
                   .catch(function(error: any) {
                     expect(error.name).toEqual('AbortError');
                     expect(logs).toEqual([
                       'scheduleTask:fetch:macroTask', 'cancelTask:fetch:macroTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
                       'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
                     ]);
                     done();
                   });
               fetchTask.zone.cancelTask(fetchTask);
             });
           }));
      });
    }));
