'use strict';

var tape = require('../');
var tap = require('tap');
var concat = require('concat-stream');
var forEach = require('for-each');
var v = require('es-value-fixtures');
var inspect = require('object-inspect');
var flatMap = require('array.prototype.flatmap');

var stripFullStack = require('./common').stripFullStack;

tap.test('teardowns', function (tt) {
	tt.plan(1);

	var test = tape.createHarness();
	test.createStream().pipe(concat(function (body) {
		tt.same(stripFullStack(body.toString('utf8')), [].concat(
			'TAP version 13',
			'# success',
			'ok 1 should be truthy',
			'# success teardown',
			'# success teardown 2',
			'# success (async)',
			'ok 2 should be truthy',
			'# success (async) teardown',
			'# success (async) teardown 2',
			'# nested teardowns',
			'# nested success',
			'ok 3 should be truthy',
			'# nested teardown (nested success level)',
			'# nested teardown (nested success level) 2',
			'# nested failure',
			'not ok 4 nested failure!',
			'  ---',
			'    operator: fail',
			'    at: Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: nested failure!',
			'          [... stack stripped ...]',
			'          at Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'# nested teardown (nested fail level)',
			'# nested teardown (nested fail level) 2',
			'# nested teardown (top level)',
			'# nested teardown (top level) 2',
			'# fail',
			'not ok 5 failure!',
			'  ---',
			'    operator: fail',
			'    at: Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: failure!',
			'          [... stack stripped ...]',
			'          at Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'# failure teardown',
			'# failure teardown 2',
			'# teardown errors do not stop the next teardown fn from running',
			'ok 6 should be truthy',
			'not ok 7 SyntaxError: teardown error!',
			'  ---',
			'    operator: fail',
			'    stack: |-',
			'      Error: SyntaxError: teardown error!',
			'          [... stack stripped ...]',
			'  ...',
			'not ok 8 plan != count',
			'  ---',
			'    operator: fail',
			'    expected: 1',
			'    actual:   2',
			'    stack: |-',
			'      Error: plan != count',
			'          [... stack stripped ...]',
			'  ...',
			'# teardown runs after teardown error',
			'# teardown given non-function fails the test',
			'ok 9 should be truthy',
			flatMap(v.nonFunctions, function (nonFunction, i) {
				var offset = 10;
				return [].concat(
					'not ok ' + (offset + (i > 0 ? i + 1 : i)) + ' teardown: ' + inspect(nonFunction) + ' is not a function',
					'  ---',
					'    operator: fail',
					'    at: <anonymous> ($TEST/teardown.js:$LINE:$COL)',
					'    stack: |-',
					'      Error: teardown: ' + inspect(nonFunction) + ' is not a function',
					'          [... stack stripped ...]',
					'          at $TEST/teardown.js:$LINE:$COL',
					'          [... stack stripped ...]',
					'          at Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
					'          [... stack stripped ...]',
					'  ...',
					i > 0 ? [] : [
						'not ok ' + (offset + 1) + ' plan != count',
						'  ---',
						'    operator: fail',
						'    expected: 1',
						'    actual:   2',
						'    at: <anonymous> ($TEST/teardown.js:$LINE:$COL)',
						'    stack: |-',
						'      Error: plan != count',
						'          [... stack stripped ...]',
						'          at $TEST/teardown.js:$LINE:$COL',
						'          [... stack stripped ...]',
						'          at Test.<anonymous> ($TEST/teardown.js:$LINE:$COL)',
						'          [... stack stripped ...]',
						'  ...'
					]
				);
			}),
			typeof Promise === 'function' ? [
				'# teardown is only ever called once, even when async',
				'ok ' + (11 + v.nonFunctions.length) + ' passes',
				'# teardown: once?',
				'# success (promise)',
				'ok ' + (12 + v.nonFunctions.length) + ' should be truthy',
				'# success (promise) teardown: 1',
				'# success (promise) teardown: 2',
				'# success (promise) teardown: 3'
			] : [
				'# SKIP teardown is only ever called once, even when async',
				'# SKIP success (promise)'
			],
			[
				'',
				'1..' + ((typeof Promise === 'function' ? 2 : 0) + 10 + v.nonFunctions.length),
				'# tests ' + ((typeof Promise === 'function' ? 2 : 0) + 10 + v.nonFunctions.length),
				'# pass  ' + ((typeof Promise === 'function' ? 2 : 0) + 5),
				'# fail  ' + (5 + v.nonFunctions.length),
				''
			]
		));
	}));

	test('success', function (t) {
		t.plan(1);
		t.teardown(function () {
			t.comment('success teardown');
		});
		t.teardown(function () {
			t.comment('success teardown 2');
		});
		t.ok('success!');
	});

	test('success (async)', function (t) {
		t.plan(1);
		t.teardown(function () {
			t.comment('success (async) teardown');
		});
		t.teardown(function () {
			t.comment('success (async) teardown 2');
		});
		setTimeout(function () {
			t.ok('success!');
		}, 10);
	});

	test('nested teardowns', function (t) {
		t.plan(2);

		t.teardown(function () {
			t.comment('nested teardown (top level)');
		});
		t.teardown(function () {
			t.comment('nested teardown (top level) 2');
		});

		t.test('nested success', function (st) {
			st.teardown(function () {
				st.comment('nested teardown (nested success level)');
			});
			st.teardown(function () {
				st.comment('nested teardown (nested success level) 2');
			});

			st.ok('nested success!');
			st.end();
		});

		t.test('nested failure', function (st) {
			st.plan(1);

			st.teardown(function () {
				st.comment('nested teardown (nested fail level)');
			});
			st.teardown(function () {
				st.comment('nested teardown (nested fail level) 2');
			});

			st.fail('nested failure!');
		});
	});

	test('fail', function (t) {
		t.plan(1);

		t.teardown(function () {
			t.comment('failure teardown');
		});
		t.teardown(function () {
			t.comment('failure teardown 2');
		});

		t.fail('failure!');
	});

	test('teardown errors do not stop the next teardown fn from running', function (t) {
		t.plan(1);

		t.ok('teardown error test');

		t.teardown(function () {
			throw new SyntaxError('teardown error!');
		});
		t.teardown(function () {
			t.comment('teardown runs after teardown error');
		});
	});

	test('teardown given non-function fails the test', function (t) {
		t.plan(1);

		t.ok('non-function test');

		forEach(v.nonFunctions, function (nonFunction) {
			t.teardown(nonFunction);
		});
	});

	test('teardown is only ever called once, even when async', { skip: typeof Promise !== 'function' }, function (t) {
		t.plan(1);

		t.teardown(function () {
			t.comment('teardown: once?');
		});

		t.pass('passes');

		return Promise.resolve();
	});

	test('success (promise)', { skip: typeof Promise !== 'function' }, function (t) {
		t.plan(1);

		t.teardown(function () {
			return new Promise(function (resolve) {
				t.comment('success (promise) teardown: 1');
				setTimeout(resolve, 10);
			}).then(function () {
				t.comment('success (promise) teardown: 2');
			});
		});
		t.teardown(function () {
			t.comment('success (promise) teardown: 3');
		});

		setTimeout(function () {
			t.ok('success!');
		}, 10);
	});
});

tap.test('teardown with promise', { skip: typeof Promise !== 'function', timeout: 1e3 }, function (tt) {
	tt.plan(2);
	tape('dummy test', function (t) {
		var resolved = false;
		t.teardown(function () {
			tt.pass('tape teardown');
			var p = Promise.resolve();
			p.then(function () {
				resolved = true;
			});
			return p;
		});
		t.on('end', function () {
			tt.is(resolved, true);
		});
		t.end();
	});
});

tap.test('teardown only runs once', { skip: typeof Promise !== 'function', timeout: 1e3 }, function (tt) {
	tt.plan(1);

	var test = tape.createHarness();
	test.createStream().pipe(concat(function (body) {
		tt.same(stripFullStack(body.toString('utf8')), [].concat(
			'TAP version 13',
			'# teardown is only called once, even with a plan',
			'ok 1 passes',
			'# Tearing down!',
			'',
			'1..1',
			'# tests 1',
			'# pass  1',
			'',
			'# ok',
			''
		));
	}));

	test('teardown is only called once, even with a plan', function (t) {
		t.plan(1);

		t.teardown(function () {
			t.comment('Tearing down!');
		});

		t.pass('passes');

		return Promise.resolve();
	});
});
